Skip to content

Commit

Permalink
Merge pull request #801 from owncloud/exposed_attributes
Browse files Browse the repository at this point in the history
Exposed attributes
  • Loading branch information
jnweiger authored Dec 7, 2023
2 parents 441a400 + af71aba commit c0e5713
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 2 deletions.
1 change: 1 addition & 0 deletions appinfo/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@
$application->checkCompatibility();
$application->registerBackends();
$application->registerHooks();
$application->registerEventListener();
13 changes: 13 additions & 0 deletions js/wizard/wizardTabAdvanced.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ OCA = OCA || {};
home_folder_naming_rule: {
$element: $('#home_folder_naming_rule'),
setMethod: 'setHomeFolderAttribute'
},
ldap_exposed_attributes_for_user: {
$element: $('#ldap_exposed_attributes_for_user'),
setMethod: 'setExposedAttributesForUser'
}
};
this.setManagedItems(items);
Expand Down Expand Up @@ -350,6 +354,15 @@ OCA = OCA || {};
this.setElementValue(this.managedItems.home_folder_naming_rule.$element, attribute);
},

/**
* sets the user attributes that will be exposed to other apps
*
* @param {string} attributes
*/
setExposedAttributesForUser: function(attributes) {
this.setElementValue(this.managedItems.ldap_exposed_attributes_for_user.$element, attributes);
},

/**
* deals with the result of the Test Connection test
*
Expand Down
28 changes: 28 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
use OCA\User_LDAP\Helper;
use OCA\User_LDAP\LDAP;
use OCA\User_LDAP\User_Proxy;
use OCP\User\UserExtendedAttributesEvent;
use OCA\User_LDAP\User\Manager;

class Application extends \OCP\AppFramework\App {
/**
Expand Down Expand Up @@ -93,4 +95,30 @@ public function registerHooks() {
'loginName2UserName'
);
}

public function registerEventListener() {
$container = $this->getContainer();
$server = $container->getServer();

$uProxy = $container->query(User_Proxy::class);
$eventDispatcher = $server->getEventDispatcher();
$eventDispatcher->addListener(UserExtendedAttributesEvent::USER_EXTENDED_ATTRIBUTES, function (UserExtendedAttributesEvent $event) use ($uProxy) {
$targetUser = $event->getUser();
if ($targetUser->getBackendClassName() !== 'LDAP') {
// If the user doesn't come from LDAP, there is nothing to do here.
return;
}

try {
$attrs = $uProxy->getExposedAttributes($targetUser->getUID());

$event->setAttributes('user_ldap_state', 'OK');
foreach ($attrs as $key => $value) {
$event->setAttributes("user_ldap_attr_{$key}", $value);
}
} catch (\Exception $e) {
$event->setAttributes('user_ldap_state', 'Error');
}
});
}
}
7 changes: 7 additions & 0 deletions lib/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
* @property string $ldapConfigurationActive,
* @property string $ldapAttributesForUserSearch,
* @property string $ldapAttributesForGroupSearch,
* @property string $ldapExposedAttributesForUser,
* @property string $ldapExperiencedAdmin,
* @property string $homeFolderNamingRule,
* @property string $hasPagedResultSupport,
Expand Down Expand Up @@ -142,6 +143,7 @@ class Configuration {
'ldapConfigurationActive' => false,
'ldapAttributesForUserSearch' => null,
'ldapAttributesForGroupSearch' => null,
'ldapExposedAttributesForUser' => null,
'ldapExperiencedAdmin' => false,
'homeFolderNamingRule' => null,
'hasPagedResultSupport' => false,
Expand Down Expand Up @@ -257,6 +259,7 @@ public function setConfiguration($config, &$applied = null) {
case 'ldapBaseGroups':
case 'ldapAttributesForUserSearch':
case 'ldapAttributesForGroupSearch':
case 'ldapExposedAttributesForUser':
case 'ldapUserFilterObjectclass':
case 'ldapUserFilterGroups':
case 'ldapGroupFilterObjectclass':
Expand Down Expand Up @@ -288,6 +291,7 @@ public function readConfiguration() {
case 'ldapBaseGroups':
case 'ldapAttributesForUserSearch':
case 'ldapAttributesForGroupSearch':
case 'ldapExposedAttributesForUser':
case 'ldapUserFilterObjectclass':
case 'ldapUserFilterGroups':
case 'ldapGroupFilterObjectclass':
Expand Down Expand Up @@ -333,6 +337,7 @@ private function getTranslatedConfig() {
case 'ldapBaseGroups':
case 'ldapAttributesForUserSearch':
case 'ldapAttributesForGroupSearch':
case 'ldapExposedAttributesForUser':
case 'ldapUserFilterObjectclass':
case 'ldapUserFilterGroups':
case 'ldapGroupFilterObjectclass':
Expand Down Expand Up @@ -546,6 +551,7 @@ public function getDefaults() {
'ldap_configuration_active' => 0,
'ldap_attributes_for_user_search' => '',
'ldap_attributes_for_group_search' => '',
'ldap_exposed_attributes_for_user' => '',
'ldap_expert_username_attr' => '',
'ldap_expert_groupname_attr' => '',
'ldap_expert_uuid_user_attr' => '',
Expand Down Expand Up @@ -606,6 +612,7 @@ public function getConfigTranslationArray() {
'ldap_configuration_active' => 'ldapConfigurationActive',
'ldap_attributes_for_user_search' => 'ldapAttributesForUserSearch',
'ldap_attributes_for_group_search' => 'ldapAttributesForGroupSearch',
'ldap_exposed_attributes_for_user' => 'ldapExposedAttributesForUser',
'ldap_expert_username_attr' => 'ldapExpertUsernameAttr',
'ldap_expert_groupname_attr' => 'ldapExpertGroupnameAttr',
'ldap_expert_uuid_user_attr' => 'ldapExpertUUIDUserAttr',
Expand Down
9 changes: 7 additions & 2 deletions lib/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
* @property array $ldapBase
* @property string $ldapIgnoreNamingRules
* @property array $ldapAttributesForGroupSearch
* @property array $ldapExposedAttributesForUser
* @property string $ldapExpertUUIDGroupAttr
* @property string $uuidAttr
* @property string $ldapUuidGroupAttribute.
Expand Down Expand Up @@ -337,6 +338,7 @@ public function getConfiguration() {
case 'ldapBaseGroups':
case 'ldapAttributesForUserSearch':
case 'ldapAttributesForGroupSearch':
case 'ldapExposedAttributesForUser':
if (\is_array($config[$configkey])) {
$result[$dbkey] = \implode("\n", $config[$configkey]);
break;
Expand Down Expand Up @@ -398,8 +400,11 @@ private function doSoftValidation() {
}

//make sure empty search attributes are saved as simple, empty array
$saKeys = ['ldapAttributesForUserSearch',
'ldapAttributesForGroupSearch'];
$saKeys = [
'ldapAttributesForUserSearch',
'ldapAttributesForGroupSearch',
'ldapExposedAttributesForUser',
];
foreach ($saKeys as $key) {
$val = $this->configuration->$key;
if (\is_array($val) && \count($val) === 1 && empty($val[0])) {
Expand Down
22 changes: 22 additions & 0 deletions lib/User/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,22 @@ private function checkAccess() {
public function getConnection() {
return $this->access->getConnection();
}

/**
* Get a list with the configured exposed attributes.
* The `getAttributes` method will contain these exposed attributes, and all
* of them will be requested. This list is public in order to know what
* attributes can be exposed to the outside.
*/
public function getExposedAttributes() {
$ldapConfig = $this->getConnection();
$exposedAttributes = $ldapConfig->ldapExposedAttributesForUser;
if ($exposedAttributes === '' || $exposedAttributes === null) {
$exposedAttributes = [];
}
return $exposedAttributes;
}

/**
* returns a list of attributes that will be processed further, e.g. quota,
* email, displayname, or others.
Expand Down Expand Up @@ -179,6 +195,12 @@ public function getAttributes($minimal = false) {
}
}

// attributes to be exposed
$exposedAttributes = $this->getExposedAttributes();
foreach ($exposedAttributes as $expAttr) {
$attributes[$expAttr] = true;
}

if ($this->ocConfig->getSystemValue('enable_avatars', true) === true && !$minimal) {
// attributes may come with big payload.

Expand Down
9 changes: 9 additions & 0 deletions lib/User/UserEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,15 @@ public function getSearchTerms() {
return \array_keys($searchTerms);
}

/**
* Get the value of the attribute or null if the attribute is missing.
* Unless the attribute has an associated converter (such as objectsid,
* guid and objectguid), the value will be returned unmodified.
*/
public function getAttribute($attrName) {
return $this->getAttributeValue($attrName, null, false);
}

/**
* @param string $configOption
* @param string $default
Expand Down
24 changes: 24 additions & 0 deletions lib/User_LDAP.php
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,30 @@ public function getAvatar($uid) {
return null;
}

/**
* Get the exposed attributes for the user.
* It will return a map with the configured attributes as keys, and the
* value of those attributes as values. In case of multivalued attributes,
* only the first value will be returned.
*
* @param string $uid
* @return array<string,string>|false key -> value map containing the attributes
* and their values. False if the user isn't found
*/
public function getExposedAttributes($uid) {
$userEntry = $this->userManager->getCachedEntry($uid);
if ($userEntry === null) {
return false;
}

$exposedAttrs = $this->userManager->getExposedAttributes();
$attrs = [];
foreach ($exposedAttrs as $attr) {
$attrs[$attr] = $userEntry->getAttribute($attr);
}
return $attrs;
}

public function clearConnectionCache() {
$this->userManager->getConnection()->clearCache();
}
Expand Down
4 changes: 4 additions & 0 deletions lib/User_Proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,10 @@ public function getBackendCount() {
return \count($this->backends);
}

public function getExposedAttributes($uid) {
return $this->handleRequest($uid, 'getExposedAttributes', [$uid]);
}

public function clearFullCache($callback = null) {
$this->clearCache();
if ($callback !== null) {
Expand Down
7 changes: 7 additions & 0 deletions templates/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,13 @@
<?php p($l->t('Leave empty for user name (default). Otherwise, specify an LDAP/AD attribute.')); ?>
</div>
</div>
<div class="tablerow">
<label for="ldap_exposed_attributes_for_user"><?php p($l->t('Exposed User Attributes')); ?></label>
<textarea id="ldap_exposed_attributes_for_user" name="ldap_exposed_attributes_for_user" placeholder="<?php p($l->t('Optional; one attribute per line')); ?>" data-default="<?php p($_['ldap_exposed_attributes_for_user_default']); ?>"></textarea>
<div class="hint">
<?php p($l->t('User attributes that other apps will be able to check')); ?>
</div>
</div>
</div>
</section>
</div>
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/User/ManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ protected function setUp(): void {
return null;
case 'ldapAttributesForUserSearch':
return ['uidNumber'];
case 'ldapExposedAttributesForUser':
return ['homePhone', 'homePostalAddress'];
case 'ldapBaseUsers':
return 'dc=foobar,dc=bar';
default:
Expand All @@ -124,6 +126,12 @@ protected function setUp(): void {
$this->manager->setLdapAccess($this->access);
}

public function testGetExposedAttributes() {
$attributes = $this->manager->getExposedAttributes();
$this->assertContains('homePhone', $attributes);
$this->assertContains('homePostalAddress', $attributes);
}

public function testGetAttributesAll() {
$this->config->expects($this->once())
->method('getSystemValue')
Expand All @@ -138,6 +146,8 @@ public function testGetAttributesAll() {
$this->assertContains('jpegphoto', $attributes);
$this->assertContains('thumbnailphoto', $attributes);
$this->assertContains('uidNumber', $attributes);
$this->assertContains('homePhone', $attributes);
$this->assertContains('homePostalAddress', $attributes);
}

public function testGetAttributesAvatarsDisabled() {
Expand Down
43 changes: 43 additions & 0 deletions tests/unit/User/UserEntryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,49 @@ public function testGetSearchTermsUnconfigured() {
self::assertEquals([], $userEntry->getSearchTerms());
}

public function testGetAttribute() {
$userEntry = new UserEntry(
$this->config,
$this->logger,
$this->connection,
[
'dn' => [0 => 'cn=foo,dc=foobar,dc=bar'],
'samaccountname' => [0 => 'user007'],
'whencreated' => [0 => '20220930084030.0Z']
]
);
self::assertSame('user007', $userEntry->getAttribute('samaccountname'));
self::assertSame('20220930084030.0Z', $userEntry->getAttribute('whencreated'));
}

public function testGetAttributeMissing() {
$userEntry = new UserEntry(
$this->config,
$this->logger,
$this->connection,
[
'dn' => [0 => 'cn=foo,dc=foobar,dc=bar'],
'samaccountname' => [0 => 'user007'],
'whencreated' => [0 => '20220930084030.0Z']
]
);
self::assertNull($userEntry->getAttribute('missingone'));
}

public function testGetAttributeNotTrimmed() {
$userEntry = new UserEntry(
$this->config,
$this->logger,
$this->connection,
[
'dn' => [0 => 'cn=foo,dc=foobar,dc=bar'],
'samaccountname' => [0 => ' user007 '],
'whencreated' => [0 => '20220930084030.0Z']
]
);
self::assertSame(' user007 ', $userEntry->getAttribute('samaccountname'));
}

public function testLdapEntryLowercasedKeys() {
$val = 'cn=foo,dc=foobar,dc=bar';
$input = ['Dn' => ['count' => 1, $val]];
Expand Down
24 changes: 24 additions & 0 deletions tests/unit/User_LDAPTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,30 @@ public function testCanChangeAvatarImageSet() {
$this->assertFalse($this->backend->canChangeAvatar('usertest'));
}

public function testGetExposedAttributes() {
$userEntry = $this->createMock(UserEntry::class);
$userEntry->method('getAttribute')
->willReturnMap([
['attr1', 'value01'],
['attr2', 'value02'],
]);

$this->manager->method('getCachedEntry')->willReturn($userEntry);
$this->manager->method('getExposedAttributes')->willReturn(['attr1', 'attr2']);

$expected = [
'attr1' => 'value01',
'attr2' => 'value02',
];
$this->assertSame($expected, $this->backend->getExposedAttributes('usertest'));
}

public function testGetExposedAttributesMissingEntry() {
$this->manager->method('getCachedEntry')->willReturn(null);

$this->assertFalse($this->backend->getExposedAttributes('usertest'));
}

public function testClearConnectionCache() {
$connection = $this->createMock(Connection::class);
$connection->expects($this->once())->method('clearCache');
Expand Down

0 comments on commit c0e5713

Please sign in to comment.