diff options
author | Simon L <szaimen@e.mail.de> | 2023-04-20 11:23:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-20 11:23:24 +0200 |
commit | 93966e99c19ec9746c9839d47486bdd2383bc82e (patch) | |
tree | 7f223ee8911cbcda59385cc360d33fb02f855507 /apps/user_ldap | |
parent | 7a704b81984ce855d35a22b3b2aa26491e838104 (diff) | |
parent | 64914593a0b3157a67621188a062e722b34976bd (diff) | |
download | nextcloud-server-93966e99c19ec9746c9839d47486bdd2383bc82e.tar.gz nextcloud-server-93966e99c19ec9746c9839d47486bdd2383bc82e.zip |
Merge pull request #36565 from march42/feature/ldap_update_profile
[user_ldap] Update profile from LDAP fields
Diffstat (limited to 'apps/user_ldap')
-rw-r--r-- | apps/user_ldap/js/wizard/wizardTabAdvanced.js | 119 | ||||
-rw-r--r-- | apps/user_ldap/lib/Configuration.php | 28 | ||||
-rw-r--r-- | apps/user_ldap/lib/Connection.php | 9 | ||||
-rw-r--r-- | apps/user_ldap/lib/User/Manager.php | 10 | ||||
-rw-r--r-- | apps/user_ldap/lib/User/User.php | 166 | ||||
-rw-r--r-- | apps/user_ldap/templates/settings.php | 12 |
6 files changed, 343 insertions, 1 deletions
diff --git a/apps/user_ldap/js/wizard/wizardTabAdvanced.js b/apps/user_ldap/js/wizard/wizardTabAdvanced.js index be98072dcb3..a438b847401 100644 --- a/apps/user_ldap/js/wizard/wizardTabAdvanced.js +++ b/apps/user_ldap/js/wizard/wizardTabAdvanced.js @@ -125,6 +125,44 @@ OCA = OCA || {}; $element: $('#ldap_ext_storage_home_attribute'), setMethod: 'setExternalStorageHomeAttribute' }, + + //User Profile Attributes + ldap_attr_phone: { + $element: $('#ldap_attr_phone'), + setMethod: 'setPhoneAttribute' + }, + ldap_attr_website: { + $element: $('#ldap_attr_website'), + setMethod: 'setWebsiteAttribute' + }, + ldap_attr_address: { + $element: $('#ldap_attr_address'), + setMethod: 'setAddressAttribute' + }, + ldap_attr_twitter: { + $element: $('#ldap_attr_twitter'), + setMethod: 'setTwitterAttribute' + }, + ldap_attr_fediverse: { + $element: $('#ldap_attr_fediverse'), + setMethod: 'setFediverseAttribute' + }, + ldap_attr_organisation: { + $element: $('#ldap_attr_organisation'), + setMethod: 'setOrganisationAttribute' + }, + ldap_attr_role: { + $element: $('#ldap_attr_role'), + setMethod: 'setRoleAttribute' + }, + ldap_attr_headline: { + $element: $('#ldap_attr_headline'), + setMethod: 'setHeadlineAttribute' + }, + ldap_attr_biography: { + $element: $('#ldap_attr_biography'), + setMethod: 'setBiographyAttribute' + }, }; this.setManagedItems(items); }, @@ -367,6 +405,87 @@ OCA = OCA || {}; }, /** + * sets the attribute for the Nextcloud user profile phone Number + * + * @param {string} attribute + */ + setPhoneAttribute: function(attribute) { + this.setElementValue(this.managedItems.ldap_attr_phone.$element, attribute); + }, + + /** + * sets the attribute for the Nextcloud user profile website + * + * @param {string} attribute + */ + setWebsiteAttribute: function(attribute) { + this.setElementValue(this.managedItems.ldap_attr_website.$element, attribute); + }, + + /** + * sets the attribute for the Nextcloud user profile postal address + * + * @param {string} attribute + */ + setAddressAttribute: function(attribute) { + this.setElementValue(this.managedItems.ldap_attr_address.$element, attribute); + }, + + /** + * sets the attribute for the Nextcloud user profile twitter + * + * @param {string} attribute + */ + setTwitterAttribute: function(attribute) { + this.setElementValue(this.managedItems.ldap_attr_twitter.$element, attribute); + }, + + /** + * sets the attribute for the Nextcloud user profile fediverse + * + * @param {string} attribute + */ + setFediverseAttribute: function(attribute) { + this.setElementValue(this.managedItems.ldap_attr_fediverse.$element, attribute); + }, + + /** + * sets the attribute for the Nextcloud user profile organisation + * + * @param {string} attribute + */ + setOrganisationAttribute: function(attribute) { + this.setElementValue(this.managedItems.ldap_attr_organisation.$element, attribute); + }, + + /** + * sets the attribute for the Nextcloud user profile role + * + * @param {string} attribute + */ + setRoleAttribute: function(attribute) { + this.setElementValue(this.managedItems.ldap_attr_role.$element, attribute); + }, + + /** + * sets the attribute for the Nextcloud user profile headline + * + * @param {string} attribute + */ + setHeadlineAttribute: function(attribute) { + this.setElementValue(this.managedItems.ldap_attr_headline.$element, attribute); + }, + + /** + * sets the attribute for the Nextcloud user profile biography + * + * @param {string} attribute + */ + setBiographyAttribute: function(attribute) { + this.setElementValue(this.managedItems.ldap_attr_biography.$element, attribute); + }, + + /** * deals with the result of the Test Connection test * * @param {WizardTabAdvanced} view diff --git a/apps/user_ldap/lib/Configuration.php b/apps/user_ldap/lib/Configuration.php index 59fac50b90b..ef64f75a9ef 100644 --- a/apps/user_ldap/lib/Configuration.php +++ b/apps/user_ldap/lib/Configuration.php @@ -10,6 +10,7 @@ * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lennart Rosam <hello@takuto.de> * @author Lukas Reschke <lukas@statuscode.ch> + * @author Marc Hefter <marchefter@march42.net> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -123,6 +124,15 @@ class Configuration { 'ldapExtStorageHomeAttribute' => null, 'ldapMatchingRuleInChainState' => self::LDAP_SERVER_FEATURE_UNKNOWN, 'ldapConnectionTimeout' => 15, + 'ldapAttributePhone' => null, + 'ldapAttributeWebsite' => null, + 'ldapAttributeAddress' => null, + 'ldapAttributeTwitter' => null, + 'ldapAttributeFediverse' => null, + 'ldapAttributeOrganisation' => null, + 'ldapAttributeRole' => null, + 'ldapAttributeHeadline' => null, + 'ldapAttributeBiography' => null, ]; public function __construct(string $configPrefix, bool $autoRead = true) { @@ -469,6 +479,15 @@ class Configuration { 'ldap_ext_storage_home_attribute' => '', 'ldap_matching_rule_in_chain_state' => self::LDAP_SERVER_FEATURE_UNKNOWN, 'ldap_connection_timeout' => 15, + 'ldap_attr_phone' => '', + 'ldap_attr_website' => '', + 'ldap_attr_address' => '', + 'ldap_attr_twitter' => '', + 'ldap_attr_fediverse' => '', + 'ldap_attr_organisation' => '', + 'ldap_attr_role' => '', + 'ldap_attr_headline' => '', + 'ldap_attr_biography' => '', ]; } @@ -535,6 +554,15 @@ class Configuration { 'ldap_matching_rule_in_chain_state' => 'ldapMatchingRuleInChainState', 'ldapIgnoreNamingRules' => 'ldapIgnoreNamingRules', // sysconfig 'ldap_connection_timeout' => 'ldapConnectionTimeout', + 'ldap_attr_phone' => 'ldapAttributePhone', + 'ldap_attr_website' => 'ldapAttributeWebsite', + 'ldap_attr_address' => 'ldapAttributeAddress', + 'ldap_attr_twitter' => 'ldapAttributeTwitter', + 'ldap_attr_fediverse' => 'ldapAttributeFediverse', + 'ldap_attr_organisation' => 'ldapAttributeOrganisation', + 'ldap_attr_role' => 'ldapAttributeRole', + 'ldap_attr_headline' => 'ldapAttributeHeadline', + 'ldap_attr_biography' => 'ldapAttributeBiography', ]; return $array; } diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php index 6700890c8c7..d8d00dd4d27 100644 --- a/apps/user_ldap/lib/Connection.php +++ b/apps/user_ldap/lib/Connection.php @@ -73,6 +73,15 @@ use Psr\Log\LoggerInterface; * @property int hasMemberOfFilterSupport * @property int useMemberOfToDetectMembership * @property string ldapMatchingRuleInChainState + * @property string ldapAttributePhone + * @property string ldapAttributeWebsite + * @property string ldapAttributeAddress + * @property string ldapAttributeTwitter + * @property string ldapAttributeFediverse + * @property string ldapAttributeOrganisation + * @property string ldapAttributeRole + * @property string ldapAttributeHeadline + * @property string ldapAttributeBiography */ class Connection extends LDAPUtility { /** diff --git a/apps/user_ldap/lib/User/Manager.php b/apps/user_ldap/lib/User/Manager.php index b1915ab57b5..04c67a537b8 100644 --- a/apps/user_ldap/lib/User/Manager.php +++ b/apps/user_ldap/lib/User/Manager.php @@ -6,6 +6,7 @@ * @author Christoph Wurst <christoph@winzerhof-wurst.at> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Marc Hefter <marchefter@march42.net> * @author Morris Jobke <hey@morrisjobke.de> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Roger Szabo <roger.szabo@web.de> @@ -152,6 +153,15 @@ class Manager { $this->access->getConnection()->ldapUserDisplayName, $this->access->getConnection()->ldapUserDisplayName2, $this->access->getConnection()->ldapExtStorageHomeAttribute, + $this->access->getConnection()->ldapAttributePhone, + $this->access->getConnection()->ldapAttributeWebsite, + $this->access->getConnection()->ldapAttributeAddress, + $this->access->getConnection()->ldapAttributeTwitter, + $this->access->getConnection()->ldapAttributeFediverse, + $this->access->getConnection()->ldapAttributeOrganisation, + $this->access->getConnection()->ldapAttributeRole, + $this->access->getConnection()->ldapAttributeHeadline, + $this->access->getConnection()->ldapAttributeBiography, ]; $homeRule = (string)$this->access->getConnection()->homeFolderNamingRule; diff --git a/apps/user_ldap/lib/User/User.php b/apps/user_ldap/lib/User/User.php index edf43494777..f6a3bf70792 100644 --- a/apps/user_ldap/lib/User/User.php +++ b/apps/user_ldap/lib/User/User.php @@ -7,6 +7,7 @@ * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Juan Pablo Villafáñez <jvillafanez@solidgear.es> + * @author Marc Hefter <marchefter@march42.net> * @author Morris Jobke <hey@morrisjobke.de> * @author Philipp Staiger <philipp@staiger.it> * @author Roger Szabo <roger.szabo@web.de> @@ -31,6 +32,8 @@ */ namespace OCA\User_LDAP\User; +use Exception; +use OC\Accounts\AccountManager; use OCA\User_LDAP\Access; use OCA\User_LDAP\Connection; use OCA\User_LDAP\Exceptions\AttributeNotSet; @@ -41,7 +44,10 @@ use OCP\ILogger; use OCP\Image; use OCP\IUser; use OCP\IUserManager; +use OCP\Accounts\IAccountManager; +use OCP\Accounts\PropertyDoesNotExistException; use OCP\Notification\IManager as INotificationManager; +use OCP\Server; use Psr\Log\LoggerInterface; /** @@ -231,6 +237,110 @@ class User { } unset($attr); + // check for cached profile data + $username = $this->getUsername(); // buffer variable, to save resource + $cacheKey = 'getUserProfile-'.$username; + $profileCached = $this->connection->getFromCache($cacheKey); + // honoring profile disabled in config.php and check if user profile was refreshed + if ($this->config->getSystemValueBool('profile.enabled', true) && + ($profileCached === null) && // no cache or TTL not expired + !$this->wasRefreshed('profile')) { + // check current data + $profileValues = array(); + //User Profile Field - Phone number + $attr = strtolower($this->connection->ldapAttributePhone); + if (isset($ldapEntry[$attr])) { + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_PHONE] + = $ldapEntry[$attr][0]; + } + //User Profile Field - website + $attr = strtolower($this->connection->ldapAttributeWebsite); + if (isset($ldapEntry[$attr])) { + $cutPosition = strpos($ldapEntry[$attr][0]," "); + if ($cutPosition) { + // drop appended label + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_WEBSITE] + = substr($ldapEntry[$attr][0],0,$cutPosition); + } else { + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_WEBSITE] + = $ldapEntry[$attr][0]; + } + } + //User Profile Field - Address + $attr = strtolower($this->connection->ldapAttributeAddress); + if (isset($ldapEntry[$attr])) { + if (str_contains($ldapEntry[$attr][0],'$')) { + // basic format conversion from postalAddress syntax to commata delimited + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_ADDRESS] + = str_replace('$', ", ", $ldapEntry[$attr][0]); + } else { + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_ADDRESS] + = $ldapEntry[$attr][0]; + } + } + //User Profile Field - Twitter + $attr = strtolower($this->connection->ldapAttributeTwitter); + if (isset($ldapEntry[$attr])) { + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_TWITTER] + = $ldapEntry[$attr][0]; + } + //User Profile Field - fediverse + $attr = strtolower($this->connection->ldapAttributeFediverse); + if (isset($ldapEntry[$attr])) { + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_FEDIVERSE] + = $ldapEntry[$attr][0]; + } + //User Profile Field - organisation + $attr = strtolower($this->connection->ldapAttributeOrganisation); + if (isset($ldapEntry[$attr])) { + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_ORGANISATION] + = $ldapEntry[$attr][0]; + } + //User Profile Field - role + $attr = strtolower($this->connection->ldapAttributeRole); + if (isset($ldapEntry[$attr])) { + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_ROLE] + = $ldapEntry[$attr][0]; + } + //User Profile Field - headline + $attr = strtolower($this->connection->ldapAttributeHeadline); + if (isset($ldapEntry[$attr])) { + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_HEADLINE] + = $ldapEntry[$attr][0]; + } + //User Profile Field - biography + $attr = strtolower($this->connection->ldapAttributeBiography); + if (isset($ldapEntry[$attr])) { + if (str_contains($ldapEntry[$attr][0],'\r')) { + // convert line endings + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_BIOGRAPHY] + = str_replace(array("\r\n","\r"), "\n", $ldapEntry[$attr][0]); + } else { + $profileValues[\OCP\Accounts\IAccountManager::PROPERTY_BIOGRAPHY] + = $ldapEntry[$attr][0]; + } + } + // check for changed data and cache just for TTL checking + $checksum = hash('sha256', json_encode($profileValues)); + $this->connection->writeToCache($cacheKey + , $checksum // write array to cache. is waste of cache space + , null); // use ldapCacheTTL from configuration + // Update user profile + if ($this->config->getUserValue($username, 'user_ldap', 'lastProfileChecksum', null) !== $checksum) { + $this->config->setUserValue($username, 'user_ldap', 'lastProfileChecksum', $checksum); + $this->updateProfile($profileValues); + $this->logger->info("updated profile uid=$username" + , ['app' => 'user_ldap']); + } else { + $this->logger->debug("profile data from LDAP unchanged" + , ['app' => 'user_ldap', 'uid' => $username]); + } + unset($attr); + } elseif ($profileCached !== null) { // message delayed, to declutter log + $this->logger->debug("skipping profile check, while cached data exist" + , ['app' => 'user_ldap', 'uid' => $username]); + } + //Avatar /** @var Connection $connection */ $connection = $this->access->getConnection(); @@ -406,7 +516,7 @@ class User { * @brief checks whether an update method specified by feature was run * already. If not, it will marked like this, because it is expected that * the method will be run, when false is returned. - * @param string $feature email | quota | avatar (can be extended) + * @param string $feature email | quota | avatar | profile (can be extended) * @return bool */ private function wasRefreshed($feature) { @@ -513,6 +623,60 @@ class User { } /** + * takes values from LDAP and stores it as Nextcloud user profile value + * + * @param array $profileValues associative array of property keys and values from LDAP + */ + private function updateProfile(array $profileValues): void { + // check if given array is empty + if (empty($profileValues)) { + return; // okay, nothing to do + } + // fetch/prepare user + $user = $this->userManager->get($this->uid); + if (is_null($user)) { + $this->logger->error('could not get user for uid='.$this->uid.'', ['app' => 'user_ldap']); + return; + } + // prepare AccountManager and Account + $accountManager = Server::get(IAccountManager::class); + $account = $accountManager->getAccount($user); // get Account + $defaultScopes = array_merge(AccountManager::DEFAULT_SCOPES, + $this->config->getSystemValue('account_manager.default_property_scope', [])); + // loop through the properties and handle them + foreach($profileValues as $property => $valueFromLDAP) { + // check and update profile properties + $value = (is_array($valueFromLDAP) ? $valueFromLDAP[0] : $valueFromLDAP); // take ONLY the first value, if multiple values specified + try { + $accountProperty = $account->getProperty($property); + $currentValue = $accountProperty->getValue(); + $scope = ($accountProperty->getScope() ? $accountProperty->getScope() + : $defaultScopes[$property]); + } + catch (PropertyDoesNotExistException $e) { // thrown at getProperty + $this->logger->error('property does not exist: '.$property + .' for uid='.$this->uid.'' + , ['app' => 'user_ldap', 'exception' => $e]); + $currentValue = ''; + $scope = $defaultScopes[$property]; + } + $verified = IAccountManager::VERIFIED; // trust the LDAP admin knew what he put there + if ($currentValue !== $value) { + $account->setProperty($property,$value,$scope,$verified); + $this->logger->debug('update user profile: '.$property.'='.$value + .' for uid='.$this->uid.'', ['app' => 'user_ldap']); + } + } + try { + $accountManager->updateAccount($account); // may throw InvalidArgumentException + } catch (\InvalidArgumentException $e) { + $this->logger->error('invalid data from LDAP: for uid='.$this->uid.'' + , ['app' => 'user_ldap', 'func' => 'updateProfile' + , 'exception' => $e]); + } + } + + /** * called by a post_login hook to save the avatar picture * * @param array $params diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index a28ef55a306..916ff84b82a 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -120,6 +120,18 @@ style('user_ldap', 'settings'); <p><label for="home_folder_naming_rule"><?php p($l->t('User Home Folder Naming Rule'));?></label><input type="text" id="home_folder_naming_rule" name="home_folder_naming_rule" aria-describedby="home_folder_naming_rule_instructions" title="<?php p($l->t('Leave empty for username (default). Otherwise, specify an LDAP/AD attribute.'));?>" data-default="<?php p($_['home_folder_naming_rule_default']); ?>" /><p class="hidden-visually" id="home_folder_naming_rule_instructions"><?php p($l->t('Leave empty for username (default). Otherwise, specify an LDAP/AD attribute.'));?></p></p> <p><label for="ldap_ext_storage_home_attribute"> <?php p($l->t('"$home" Placeholder Field')); ?></label><input type="text" id="ldap_ext_storage_home_attribute" name="ldap_ext_storage_home_attribute" aria-describedby="ldap_ext_storage_home_attribute_instructions" title="<?php p($l->t('$home in an external storage configuration will be replaced with the value of the specified attribute')); ?>" data-default="<?php p($_['ldap_ext_storage_home_attribute_default']); ?>"><p class="hidden-visually" id="ldap_ext_storage_home_attribute_instructions"><?php p($l->t('$home in an external storage configuration will be replaced with the value of the specified attribute')); ?></p></p> </div> + <h3><?php p($l->t('User Profile Attributes'));?></h3> + <div> + <p><label for="ldap_attr_phone"> <?php p($l->t('Phone Field')); ?></label><input type="text" id="ldap_attr_phone" name="ldap_attr_phone" title="<?php p($l->t('User profile Phone will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_phone_default']); ?>"></p> + <p><label for="ldap_attr_website"> <?php p($l->t('Website Field')); ?></label><input type="text" id="ldap_attr_website" name="ldap_attr_website" title="<?php p($l->t('User profile Website will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_website_default']); ?>"></p> + <p><label for="ldap_attr_address"> <?php p($l->t('Address Field')); ?></label><input type="text" id="ldap_attr_address" name="ldap_attr_address" title="<?php p($l->t('User profile Address will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_address_default']); ?>"></p> + <p><label for="ldap_attr_twitter"> <?php p($l->t('Twitter Field')); ?></label><input type="text" id="ldap_attr_twitter" name="ldap_attr_twitter" title="<?php p($l->t('User profile Twitter will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_twitter_default']); ?>"></p> + <p><label for="ldap_attr_fediverse"> <?php p($l->t('Fediverse Field')); ?></label><input type="text" id="ldap_attr_fediverse" name="ldap_attr_fediverse" title="<?php p($l->t('User profile Fediverse will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_fediverse_default']); ?>"></p> + <p><label for="ldap_attr_organisation"> <?php p($l->t('Organisation Field')); ?></label><input type="text" id="ldap_attr_organisation" name="ldap_attr_organisation" title="<?php p($l->t('User profile Organisation will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_organisation_default']); ?>"></p> + <p><label for="ldap_attr_role"> <?php p($l->t('Role Field')); ?></label><input type="text" id="ldap_attr_role" name="ldap_attr_role" title="<?php p($l->t('User profile Role will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_role_default']); ?>"></p> + <p><label for="ldap_attr_headline"> <?php p($l->t('Headline Field')); ?></label><input type="text" id="ldap_attr_headline" name="ldap_attr_headline" title="<?php p($l->t('User profile Headline will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_headline_default']); ?>"></p> + <p><label for="ldap_attr_biography"> <?php p($l->t('Biography Field')); ?></label><input type="text" id="ldap_attr_biography" name="ldap_attr_biography" title="<?php p($l->t('User profile Biography will be set from the specified attribute')); ?>" data-default="<?php p($_['ldap_attr_biography_default']); ?>"></p> + </div> </div> <?php print_unescaped($_['settingControls']); ?> </fieldset> |