diff --git a/lam/HISTORY b/lam/HISTORY index b23f17e9..79873fda 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -1,7 +1,9 @@ December 2018 6.6 - New import/export in tools menu - YubiKey support - - Windows users: manage "departmentNumber" (needs to be activated via LAM server profile) + - Windows users: + -> Manage "departmentNumber" (needs to be activated via LAM server profile) + -> Sync group memberships from Unix and group of names - LAM Pro: -> Easy setting of background color in self service profile -> Cron jobs: added Windows/Qmail/FreeRadius account expiration notification jobs diff --git a/lam/docs/manual-sources/chapter-modules.xml b/lam/docs/manual-sources/chapter-modules.xml index 837d79fb..67042ae2 100644 --- a/lam/docs/manual-sources/chapter-modules.xml +++ b/lam/docs/manual-sources/chapter-modules.xml @@ -1186,7 +1186,7 @@
- Windows (Samba 4) + Windows (Samba 4/Active Directory) Please activate the account type "Users" in your LAM server profile and then add the user module "Windows (windowsUser)(*)". @@ -1218,10 +1218,14 @@ NIS support is deactivated by default. Enable it if needed. + You can also set maximum values for user photos in advanced + options. + - + diff --git a/lam/docs/manual-sources/images/mod_windowsUser5.png b/lam/docs/manual-sources/images/mod_windowsUser5.png index 8077ed56..2f876ae0 100644 Binary files a/lam/docs/manual-sources/images/mod_windowsUser5.png and b/lam/docs/manual-sources/images/mod_windowsUser5.png differ diff --git a/lam/lib/modules/posixAccount.inc b/lam/lib/modules/posixAccount.inc index 6af27bfa..5ed738eb 100644 --- a/lam/lib/modules/posixAccount.inc +++ b/lam/lib/modules/posixAccount.inc @@ -408,6 +408,10 @@ class posixAccount extends baseModule implements passwordService { 'noObjectClass' => array( "Headline" => _("Do not add object class"), "Text" => _("This will not add the posixAccount object class to the account.") + ), + 'excludeFromGroupSync' => array ( + "Headline" => _('Exclude from group sync'), + "Text" => _('Enter one group per line that should be ignored when syncing groups.') ), 'user' => array( 'uid' => array( @@ -1447,7 +1451,7 @@ class posixAccount extends baseModule implements passwordService { * @param $allGons list of all group of names * @return string cn value */ - private function getGonName($dn, &$allGons) { + public function getGonName($dn, &$allGons) { if (!empty($allGons[$dn]['cn'][0])) { return $allGons[$dn]['cn'][0]; } @@ -2249,7 +2253,7 @@ class posixAccount extends baseModule implements passwordService { $syncGroupsCheckbox = new htmlResponsiveInputCheckbox('posixAccount_' . $typeId . '_syncGroups', false, _('Sync groups'), null, false); $syncGroupsCheckbox->setTableRowsToHide(array('posixAccount_' . $typeId . '_syncGroupsExclusions')); $configUserContainer->add($syncGroupsCheckbox, 12); - $configUserContainer->add(new htmlResponsiveInputTextarea('posixAccount_' . $typeId . '_syncGroupsExclusions', '', 20, 4, _('Exclude from group sync')), 12); + $configUserContainer->add(new htmlResponsiveInputTextarea('posixAccount_' . $typeId . '_syncGroupsExclusions', '', 20, 4, _('Exclude from group sync'), 'excludeFromGroupSync'), 12); } } $return[] = $configUserContainer; @@ -3485,7 +3489,7 @@ class posixAccount extends baseModule implements passwordService { * * @return array groups array(dn => array('cn' => array('groupName'), 'objectclass' => array('top', 'groupOfNames'))) */ - private function findGroupOfNames() { + public function findGroupOfNames() { if ($this->gonCache != null) { return $this->gonCache; } @@ -3948,6 +3952,24 @@ class posixAccount extends baseModule implements passwordService { return $replacements; } + /** + * Returns the current group names. + * + * @return string[] group names + */ + public function getGroups() { + return $this->groups; + } + + /** + * Returns the list of group of names where this user is member. + * + * @return string[] list of DNs + */ + public function getGroupOfNames() { + return $this->gonList; + } + } ?> diff --git a/lam/lib/modules/windowsUser.inc b/lam/lib/modules/windowsUser.inc index 38885919..06bbea78 100644 --- a/lam/lib/modules/windowsUser.inc +++ b/lam/lib/modules/windowsUser.inc @@ -400,6 +400,10 @@ class windowsUser extends baseModule implements passwordService { "Headline" => _("Add photo"), 'attr' => 'jpegPhoto', "Text" => _("Please select an image file to upload. It must be in JPG format (.jpg/.jpeg).") ), + 'excludeFromGroupSync' => array ( + "Headline" => _('Exclude from group sync'), + "Text" => _('Enter one group per line that should be ignored when syncing groups.') + ), ); // upload fields $return['upload_columns'] = array( @@ -1745,7 +1749,7 @@ class windowsUser extends baseModule implements passwordService { * * @return htmlElement meta HTML code */ - function display_html_group() { + public function display_html_group() { $return = new htmlTable(); $groups = $this->findGroups(); // sort by DN @@ -1796,6 +1800,51 @@ class windowsUser extends baseModule implements passwordService { $return->addElement($groupContainer); $return->addNewLine(); + // sync options + $typeManager = new TypeManager(); + $syncTypes = $typeManager->getConfiguredTypesForScopes(array('group', 'gon', 'user')); + $syncActive = false; + $syncUnixActive = false; + $syncGonActive = false; + $possibleGonSyncModules = array('groupOfNames', 'groupOfMembers', 'groupOfUniqueNames'); + foreach ($syncTypes as $syncType) { + $modules = $syncType->getModules(); + foreach ($possibleGonSyncModules as $possibleModule) { + if (in_array($possibleModule, $modules)) { + $syncActive = true; + $syncGonActive = true; + break; + } + } + } + if (in_array('posixAccount', $this->getAccountContainer()->get_type()->getModules())) { + $syncActive = true; + $syncUnixActive = true; + } + $syncActive = $syncActive && !$this->isBooleanConfigOptionSet('windowsUser_syncGroups'); + if ($syncActive) { + $return->addElement(new htmlSubTitle(_('Sync groups')), true); + $syncOptionTable = new htmlTable(); + $syncOptionTable->addElement(new htmlTableExtendedInputCheckbox('syncDeleteGroups', true, _('Delete non-matching entries')), true); + $return->addElement($syncOptionTable, true); + $return->addVerticalSpace('1rem'); + $syncButtons = new htmlGroup(); + if ($syncUnixActive) { + $u2wButton = new htmlAccountPageButton(get_class($this), 'group', 'syncU2W', _('Sync Unix to Windows')); + $u2wButton->setIconClass('unixButton'); + $syncButtons->addElement($u2wButton); + $syncButtons->addElement(new htmlSpacer('2rem', null)); + } + if ($syncGonActive) { + $g2wButton = new htmlAccountPageButton(get_class($this), 'group', 'syncG2W', _('Sync group of names to Windows')); + $g2wButton->setIconClass('groupButton'); + $syncButtons->addElement($g2wButton); + $syncButtons->addElement(new htmlSpacer('2rem', null)); + } + $return->addElement($syncButtons, true); + $return->addElement(new htmlSpacer(null, '2rem'), true); + } + $backGroup = new htmlGroup(); $backGroup->colspan = 10; $backGroup->addElement(new htmlSpacer(null, '10px'), true); @@ -1811,7 +1860,7 @@ class windowsUser extends baseModule implements passwordService { * * @return array list of info/error messages */ - function process_group() { + public function process_group() { if (isset($_POST['addgroups']) && isset($_POST['addgroups_button'])) { // Add groups to list // add new group $this->groupList = @array_merge($this->groupList, $_POST['addgroups']); @@ -1819,9 +1868,118 @@ class windowsUser extends baseModule implements passwordService { elseif (isset($_POST['removegroups']) && isset($_POST['removegroups_button'])) { // remove groups from list $this->groupList = array_delete($_POST['removegroups'], $this->groupList); } + // sync Unix to Windows + if (isset($_POST['form_subpage_windowsUser_group_syncU2W'])) { + $this->manualSyncUnixToWindows(); + } + // sync group of names to Windows + if (isset($_POST['form_subpage_windowsUser_group_syncG2W'])) { + $this->manualSyncGonToWindows(); + } return array(); } + /** + * Syncs the Unix groups to Windows. + */ + private function manualSyncUnixToWindows() { + $windowsGroups = $this->getGroupList(); + $unixGroups = $this->getAccountContainer()->getAccountModule('posixAccount')->getGroups(); + $allWindowsGroups = searchLDAPByAttribute('cn', '*', 'group', array('cn'), array('group')); + $dnToCn = array(); + foreach ($allWindowsGroups as $windowsGroup) { + $dnToCn[$windowsGroup['dn']] = $windowsGroup['cn'][0]; + } + $cnToDn = array_flip($dnToCn); + $currentGroupNames = array(); + foreach ($windowsGroups as $windowsGroup) { + $currentGroupNames[] = $dnToCn[$windowsGroup]; + } + $deleteNonMatching = isset($_POST['syncDeleteGroups']) && ($_POST['syncDeleteGroups'] == 'on'); + $namesToIgnore = array(); + if (!empty($this->moduleSettings['windowsUser_syncGroupsExclusions'])) { + $namesToIgnore = $this->moduleSettings['windowsUser_syncGroupsExclusions']; + array_map('trim', $namesToIgnore); + } + foreach ($unixGroups as $unixGroup) { + if (in_array($unixGroup, $namesToIgnore)) { + continue; + } + if (!in_array($unixGroup, $currentGroupNames) && isset($cnToDn[$unixGroup])) { + $windowsGroups[] = $cnToDn[$unixGroup]; + } + } + if ($deleteNonMatching) { + foreach ($currentGroupNames as $currentGroupName) { + if (in_array($currentGroupName, $namesToIgnore)) { + continue; + } + if (!in_array($currentGroupName, $unixGroups)) { + foreach ($windowsGroups as $windowsGroup) { + if ($dnToCn[$windowsGroup] == $currentGroupName) { + $windowsGroups = array_delete(array($windowsGroup), $windowsGroups); + break; + } + } + } + } + } + $this->groupList = $windowsGroups; + } + + /** + * Syncs the group of names to Windows. + */ + private function manualSyncGonToWindows() { + $windowsGroups = $this->getGroupList(); + $gonGroupDns = $this->getAccountContainer()->getAccountModule('posixAccount')->getGroupOfNames(); + $allGons = $this->getAccountContainer()->getAccountModule('posixAccount')->findGroupOfNames(); + $gonGroups = array(); + foreach ($gonGroupDns as $gonGroupDn) { + $gonGroups[] = $this->getAccountContainer()->getAccountModule('posixAccount')->getGonName($gonGroupDn, $allGons); + } + $allWindowsGroups = searchLDAPByAttribute('cn', '*', 'group', array('cn'), array('group')); + $dnToCn = array(); + foreach ($allWindowsGroups as $windowsGroup) { + $dnToCn[$windowsGroup['dn']] = $windowsGroup['cn'][0]; + } + $cnToDn = array_flip($dnToCn); + $currentGroupNames = array(); + foreach ($windowsGroups as $windowsGroup) { + $currentGroupNames[] = $dnToCn[$windowsGroup]; + } + $deleteNonMatching = isset($_POST['syncDeleteGroups']) && ($_POST['syncDeleteGroups'] == 'on'); + $namesToIgnore = array(); + if (!empty($this->moduleSettings['windowsUser_syncGroupsExclusions'])) { + $namesToIgnore = $this->moduleSettings['windowsUser_syncGroupsExclusions']; + array_map('trim', $namesToIgnore); + } + foreach ($gonGroups as $gonGroup) { + if (in_array($gonGroup, $namesToIgnore)) { + continue; + } + if (!in_array($gonGroup, $currentGroupNames) && isset($cnToDn[$gonGroup])) { + $windowsGroups[] = $cnToDn[$gonGroup]; + } + } + if ($deleteNonMatching) { + foreach ($currentGroupNames as $currentGroupName) { + if (in_array($currentGroupName, $namesToIgnore)) { + continue; + } + if (!in_array($currentGroupName, $gonGroups)) { + foreach ($windowsGroups as $windowsGroup) { + if ($dnToCn[$windowsGroup] == $currentGroupName) { + $windowsGroups = array_delete(array($windowsGroup), $windowsGroups); + break; + } + } + } + } + } + $this->groupList = $windowsGroups; + } + /** * Displays the photo upload page. * @@ -3413,6 +3571,7 @@ class windowsUser extends baseModule implements passwordService { * @see baseModule::get_configOptions() */ public function get_configOptions($scopes, $allScopes) { + $typeManager = new TypeManager($_SESSION['conf_config']); // configuration options $configContainer = new htmlResponsiveRow(); $configContainer->add(new htmlResponsiveInputTextarea('windowsUser_domains', '', 30, 3, _('Domains'), 'domains'), 12); @@ -3443,8 +3602,24 @@ class windowsUser extends baseModule implements passwordService { $configContainer->add(new htmlResponsiveInputCheckbox('windowsUser_hideou', true, _('Organisational unit'), null, true), 12, 4); $configContainer->add(new htmlResponsiveInputCheckbox('windowsUser_hideo', true, _('Organisation'), null, true), 12, 4); $configContainer->add(new htmlResponsiveInputCheckbox('windowsUser_hidemanager', true, _('Manager'), null, true), 12, 4); - for ($i = 0; $i < 1; $i++) { - $configContainer->add(new htmlOutputText(''), 0, 4); + $syncTypes = $typeManager->getConfiguredTypesForScopes(array('group', 'gon', 'user')); + $syncActive = false; + $possibleSyncModules = array('groupOfNames', 'groupOfMembers', 'groupOfUniqueNames', 'posixAccount'); + foreach ($syncTypes as $syncType) { + $modules = $syncType->getModules(); + foreach ($possibleSyncModules as $possibleModule) { + if (in_array($possibleModule, $modules)) { + $syncActive = true; + break; + } + } + } + if ($syncActive) { + $syncGroupsCheckbox = new htmlResponsiveInputCheckbox('windowsUser_syncGroups', false, _('Sync groups'), null, true); + $syncGroupsCheckbox->setTableRowsToHide(array('windowsUser_syncGroupsExclusions')); + $configContainer->add($syncGroupsCheckbox, 12, 4); + $configContainer->addVerticalSpacer('2rem'); + $configContainer->add(new htmlResponsiveInputTextarea('windowsUser_syncGroupsExclusions', '', 20, 4, _('Exclude from group sync'), 'excludeFromGroupSync'), 12); } $advancedOptions = new htmlResponsiveRow(); $advancedOptions->add(new htmlSubTitle(_('Photo')), 12);