Merge pull request #9214 from owncloud/wizard_email_detection

LDAP: Wizard email attribute detection
This commit is contained in:
Morris Jobke 2014-08-29 12:58:51 +02:00
commit 033b0361ed
5 changed files with 272 additions and 40 deletions

View File

@ -39,13 +39,29 @@ if(!isset($_POST['ldap_serverconfig_chooser'])) {
} }
$prefix = $_POST['ldap_serverconfig_chooser']; $prefix = $_POST['ldap_serverconfig_chooser'];
$ldapWrapper = new OCA\user_ldap\lib\LDAP(); $ldapWrapper = new \OCA\user_ldap\lib\LDAP();
$configuration = new \OCA\user_ldap\lib\Configuration($prefix); $configuration = new \OCA\user_ldap\lib\Configuration($prefix);
$wizard = new \OCA\user_ldap\lib\Wizard($configuration, $ldapWrapper);
$con = new \OCA\user_ldap\lib\Connection($ldapWrapper, '', null);
$con->setConfiguration($configuration->getConfiguration());
$con->ldapConfigurationActive = true;
$con->setIgnoreValidation(true);
$userManager = new \OCA\user_ldap\lib\user\Manager(
\OC::$server->getConfig(),
new \OCA\user_ldap\lib\FilesystemHelper(),
new \OCA\user_ldap\lib\LogWrapper(),
\OC::$server->getAvatarManager(),
new \OCP\Image());
$access = new \OCA\user_ldap\lib\Access($con, $ldapWrapper, $userManager);
$wizard = new \OCA\user_ldap\lib\Wizard($configuration, $ldapWrapper, $access);
switch($action) { switch($action) {
case 'guessPortAndTLS': case 'guessPortAndTLS':
case 'guessBaseDN': case 'guessBaseDN':
case 'detectEmailAttribute':
case 'determineGroupMemberAssoc': case 'determineGroupMemberAssoc':
case 'determineUserObjectClasses': case 'determineUserObjectClasses':
case 'determineGroupObjectClasses': case 'determineGroupObjectClasses':

View File

@ -14,7 +14,7 @@ function LdapFilter(target) {
} }
} }
LdapFilter.prototype.compose = function() { LdapFilter.prototype.compose = function(callback) {
var action; var action;
if(this.locked) { if(this.locked) {
@ -50,6 +50,9 @@ LdapFilter.prototype.compose = function() {
LdapWizard.countGroups(); LdapWizard.countGroups();
LdapWizard.detectGroupMemberAssoc(); LdapWizard.detectGroupMemberAssoc();
} }
if(typeof callback !== 'undefined') {
callback();
}
}, },
function () { function () {
console.log('LDAP Wizard: could not compose filter. '+ console.log('LDAP Wizard: could not compose filter. '+

View File

@ -340,6 +340,14 @@ var LdapWizard = {
LdapWizard._countThings('countUsers'); LdapWizard._countThings('countUsers');
}, },
detectEmailAttribute: function() {
param = 'action=detectEmailAttribute'+
'&ldap_serverconfig_chooser='+
encodeURIComponent($('#ldap_serverconfig_chooser').val());
//runs in the background, no callbacks necessary
LdapWizard.ajax(param, LdapWizard.applyChanges, function(){});
},
detectGroupMemberAssoc: function() { detectGroupMemberAssoc: function() {
param = 'action=determineGroupMemberAssoc'+ param = 'action=determineGroupMemberAssoc'+
'&ldap_serverconfig_chooser='+ '&ldap_serverconfig_chooser='+
@ -577,7 +585,7 @@ var LdapWizard = {
postInitUserFilter: function() { postInitUserFilter: function() {
if(LdapWizard.userFilterObjectClassesHasRun && if(LdapWizard.userFilterObjectClassesHasRun &&
LdapWizard.userFilterAvailableGroupsHasRun) { LdapWizard.userFilterAvailableGroupsHasRun) {
LdapWizard.userFilter.compose(); LdapWizard.userFilter.compose(LdapWizard.detectEmailAttribute);
LdapWizard.countUsers(); LdapWizard.countUsers();
} }
}, },
@ -619,6 +627,7 @@ var LdapWizard = {
if(triggerObj.id == 'ldap_userlist_filter') { if(triggerObj.id == 'ldap_userlist_filter') {
LdapWizard.countUsers(); LdapWizard.countUsers();
LdapWizard.detectEmailAttribute();
} else if(triggerObj.id == 'ldap_group_filter') { } else if(triggerObj.id == 'ldap_group_filter') {
LdapWizard.countGroups(); LdapWizard.countGroups();
LdapWizard.detectGroupMemberAssoc(); LdapWizard.detectGroupMemberAssoc();
@ -656,9 +665,12 @@ var LdapWizard = {
LdapWizard._save($('#'+originalObj)[0], $.trim(values)); LdapWizard._save($('#'+originalObj)[0], $.trim(values));
if(originalObj == 'ldap_userfilter_objectclass' if(originalObj == 'ldap_userfilter_objectclass'
|| originalObj == 'ldap_userfilter_groups') { || originalObj == 'ldap_userfilter_groups') {
LdapWizard.userFilter.compose(); LdapWizard.userFilter.compose(LdapWizard.detectEmailAttribute);
//when user filter is changed afterwards, login filter needs to //when user filter is changed afterwards, login filter needs to
//be adjusted, too //be adjusted, too
if(!LdapWizard.loginFilter) {
LdapWizard.initLoginFilter();
}
LdapWizard.loginFilter.compose(); LdapWizard.loginFilter.compose();
} else if(originalObj == 'ldap_loginfilter_attributes') { } else if(originalObj == 'ldap_loginfilter_attributes') {
LdapWizard.loginFilter.compose(); LdapWizard.loginFilter.compose();
@ -720,7 +732,7 @@ var LdapWizard = {
LdapWizard._save({ id: modeKey }, LdapWizard.filterModeAssisted); LdapWizard._save({ id: modeKey }, LdapWizard.filterModeAssisted);
if(moc.indexOf('user') >= 0) { if(moc.indexOf('user') >= 0) {
LdapWizard.blacklistRemove('ldap_userlist_filter'); LdapWizard.blacklistRemove('ldap_userlist_filter');
LdapWizard.userFilter.compose(); LdapWizard.userFilter.compose(LdapWizard.detectEmailAttribute);
} else { } else {
LdapWizard.blacklistRemove('ldap_group_filter'); LdapWizard.blacklistRemove('ldap_group_filter');
LdapWizard.groupFilter.compose(); LdapWizard.groupFilter.compose();

View File

@ -25,6 +25,7 @@ namespace OCA\user_ldap\lib;
class Wizard extends LDAPUtility { class Wizard extends LDAPUtility {
static protected $l; static protected $l;
protected $access;
protected $cr; protected $cr;
protected $configuration; protected $configuration;
protected $result; protected $result;
@ -48,12 +49,13 @@ class Wizard extends LDAPUtility {
* @param Configuration $configuration an instance of Configuration * @param Configuration $configuration an instance of Configuration
* @param ILDAPWrapper $ldap an instance of ILDAPWrapper * @param ILDAPWrapper $ldap an instance of ILDAPWrapper
*/ */
public function __construct(Configuration $configuration, ILDAPWrapper $ldap) { public function __construct(Configuration $configuration, ILDAPWrapper $ldap, Access $access) {
parent::__construct($ldap); parent::__construct($ldap);
$this->configuration = $configuration; $this->configuration = $configuration;
if(is_null(Wizard::$l)) { if(is_null(Wizard::$l)) {
Wizard::$l = \OC_L10N::get('user_ldap'); Wizard::$l = \OC_L10N::get('user_ldap');
} }
$this->access = $access;
$this->result = new WizardResult; $this->result = new WizardResult;
} }
@ -78,11 +80,10 @@ class Wizard extends LDAPUtility {
throw new \Exception('Requirements not met', 400); throw new \Exception('Requirements not met', 400);
} }
$ldapAccess = $this->getAccess();
if($type === 'groups') { if($type === 'groups') {
$result = $ldapAccess->countGroups($filter); $result = $this->access->countGroups($filter);
} else if($type === 'users') { } else if($type === 'users') {
$result = $ldapAccess->countUsers($filter); $result = $this->access->countUsers($filter);
} else { } else {
throw new \Exception('internal error: invald object type', 500); throw new \Exception('internal error: invald object type', 500);
} }
@ -128,6 +129,77 @@ class Wizard extends LDAPUtility {
return $this->result; return $this->result;
} }
/**
* counts users with a specified attribute
* @param string $attr
* @return int|bool
*/
public function countUsersWithAttribute($attr) {
if(!$this->checkRequirements(array('ldapHost',
'ldapPort',
'ldapBase',
'ldapUserFilter',
))) {
return false;
}
$filter = $this->access->combineFilterWithAnd(array(
$this->configuration->ldapUserFilter,
$attr . '=*'
));
return $this->access->countUsers($filter);
}
/**
* detects the most often used email attribute for users applying to the
* user list filter. If a setting is already present that returns at least
* one hit, the detection will be canceled.
* @return WizardResult|bool
*/
public function detectEmailAttribute() {
if(!$this->checkRequirements(array('ldapHost',
'ldapPort',
'ldapBase',
'ldapUserFilter',
))) {
return false;
}
$attr = $this->configuration->ldapEmailAttribute;
if(!empty($attr)) {
$count = intval($this->countUsersWithAttribute($attr));
if($count > 0) {
return false;
}
$writeLog = true;
} else {
$writeLog = false;
}
$emailAttributes = array('mail', 'mailPrimaryAddress');
$winner = '';
$maxUsers = 0;
foreach($emailAttributes as $attr) {
$count = $this->countUsersWithAttribute($attr);
if($count > $maxUsers) {
$maxUsers = $count;
$winner = $attr;
}
}
if($winner !== '') {
$this->result->addChange('ldap_email_attr', $winner);
if($writeLog) {
\OCP\Util::writeLog('user_ldap', 'The mail attribute has ' .
'automatically been reset, because the original value ' .
'did not return any results.', \OCP\Util::INFO);
}
}
return $this->result;
}
/** /**
* @return WizardResult * @return WizardResult
* @throws \Exception * @throws \Exception
@ -289,7 +361,6 @@ class Wizard extends LDAPUtility {
*/ */
public function fetchGroups($dbKey, $confKey) { public function fetchGroups($dbKey, $confKey) {
$obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames'); $obclasses = array('posixGroup', 'group', 'zimbraDistributionList', 'groupOfNames');
$ldapAccess = $this->getAccess();
$filterParts = array(); $filterParts = array();
foreach($obclasses as $obclass) { foreach($obclasses as $obclass) {
@ -298,15 +369,15 @@ class Wizard extends LDAPUtility {
//we filter for everything //we filter for everything
//- that looks like a group and //- that looks like a group and
//- has the group display name set //- has the group display name set
$filter = $ldapAccess->combineFilterWithOr($filterParts); $filter = $this->access->combineFilterWithOr($filterParts);
$filter = $ldapAccess->combineFilterWithAnd(array($filter, 'cn=*')); $filter = $this->access->combineFilterWithAnd(array($filter, 'cn=*'));
$groupNames = array(); $groupNames = array();
$groupEntries = array(); $groupEntries = array();
$limit = 400; $limit = 400;
$offset = 0; $offset = 0;
do { do {
$result = $ldapAccess->searchGroups($filter, array('cn','dn'), $limit, $offset); $result = $this->access->searchGroups($filter, array('cn'), $limit, $offset);
foreach($result as $item) { foreach($result as $item) {
$groupNames[] = $item['cn']; $groupNames[] = $item['cn'];
$groupEntries[] = $item; $groupEntries[] = $item;
@ -1104,27 +1175,6 @@ class Wizard extends LDAPUtility {
} }
} }
/**
* creates and returns an Access instance
* @return \OCA\user_ldap\lib\Access
*/
private function getAccess() {
$con = new Connection($this->ldap, '', null);
$con->setConfiguration($this->configuration->getConfiguration());
$con->ldapConfigurationActive = true;
$con->setIgnoreValidation(true);
$userManager = new user\Manager(
\OC::$server->getConfig(),
new FilesystemHelper(),
new LogWrapper(),
\OC::$server->getAvatarManager(),
new \OCP\Image());
$ldapAccess = new Access($con, $this->ldap, $userManager);
return $ldapAccess;
}
/** /**
* @return bool|mixed * @return bool|mixed
*/ */

View File

@ -43,16 +43,29 @@ class Test_Wizard extends \PHPUnit_Framework_TestCase {
} }
private function getWizardAndMocks() { private function getWizardAndMocks() {
static $conMethods; static $confMethods;
static $connMethods;
static $accMethods;
if(is_null($conMethods)) { if(is_null($confMethods)) {
$conMethods = get_class_methods('\OCA\user_ldap\lib\Configuration'); $confMethods = get_class_methods('\OCA\user_ldap\lib\Configuration');
$connMethods = get_class_methods('\OCA\user_ldap\lib\Connection');
$accMethods = get_class_methods('\OCA\user_ldap\lib\Access');
} }
$lw = $this->getMock('\OCA\user_ldap\lib\ILDAPWrapper'); $lw = $this->getMock('\OCA\user_ldap\lib\ILDAPWrapper');
$conf = $this->getMock('\OCA\user_ldap\lib\Configuration', $conf = $this->getMock('\OCA\user_ldap\lib\Configuration',
$conMethods, $confMethods,
array($lw, null, null)); array($lw, null, null));
return array(new Wizard($conf, $lw), $conf, $lw);
$connector = $this->getMock('\OCA\user_ldap\lib\Connection',
$connMethods, array($lw, null, null));
$um = $this->getMockBuilder('\OCA\user_ldap\lib\user\Manager')
->disableOriginalConstructor()
->getMock();
$access = $this->getMock('\OCA\user_ldap\lib\Access',
$accMethods, array($connector, $lw, $um));
return array(new Wizard($conf, $lw, $access), $conf, $lw, $access);
} }
private function prepareLdapWrapperForConnections(&$ldap) { private function prepareLdapWrapperForConnections(&$ldap) {
@ -207,6 +220,144 @@ class Test_Wizard extends \PHPUnit_Framework_TestCase {
unset($uidnumber); unset($uidnumber);
} }
public function testDetectEmailAttributeAlreadySet() {
list($wizard, $configuration, $ldap, $access)
= $this->getWizardAndMocks();
$configuration->expects($this->any())
->method('__get')
->will($this->returnCallback(function ($name) {
if($name === 'ldapEmailAttribute') {
return 'myEmailAttribute';
} else {
//for requirement checks
return 'let me pass';
}
}));
$access->expects($this->once())
->method('countUsers')
->will($this->returnValue(42));
$wizard->detectEmailAttribute();
}
public function testDetectEmailAttributeOverrideSet() {
list($wizard, $configuration, $ldap, $access)
= $this->getWizardAndMocks();
$configuration->expects($this->any())
->method('__get')
->will($this->returnCallback(function ($name) {
if($name === 'ldapEmailAttribute') {
return 'myEmailAttribute';
} else {
//for requirement checks
return 'let me pass';
}
}));
$access->expects($this->exactly(3))
->method('combineFilterWithAnd')
->will($this->returnCallback(function ($filterParts) {
return str_replace('=*', '', array_pop($filterParts));
}));
$access->expects($this->exactly(3))
->method('countUsers')
->will($this->returnCallback(function ($filter) {
if($filter === 'myEmailAttribute') {
return 0;
} else if($filter === 'mail') {
return 3;
} else if($filter === 'mailPrimaryAddress') {
return 17;
}
var_dump($filter);
}));
$result = $wizard->detectEmailAttribute()->getResultArray();
$this->assertSame('mailPrimaryAddress',
$result['changes']['ldap_email_attr']);
}
public function testDetectEmailAttributeFind() {
list($wizard, $configuration, $ldap, $access)
= $this->getWizardAndMocks();
$configuration->expects($this->any())
->method('__get')
->will($this->returnCallback(function ($name) {
if($name === 'ldapEmailAttribute') {
return '';
} else {
//for requirement checks
return 'let me pass';
}
}));
$access->expects($this->exactly(2))
->method('combineFilterWithAnd')
->will($this->returnCallback(function ($filterParts) {
return str_replace('=*', '', array_pop($filterParts));
}));
$access->expects($this->exactly(2))
->method('countUsers')
->will($this->returnCallback(function ($filter) {
if($filter === 'myEmailAttribute') {
return 0;
} else if($filter === 'mail') {
return 3;
} else if($filter === 'mailPrimaryAddress') {
return 17;
}
var_dump($filter);
}));
$result = $wizard->detectEmailAttribute()->getResultArray();
$this->assertSame('mailPrimaryAddress',
$result['changes']['ldap_email_attr']);
}
public function testDetectEmailAttributeFindNothing() {
list($wizard, $configuration, $ldap, $access)
= $this->getWizardAndMocks();
$configuration->expects($this->any())
->method('__get')
->will($this->returnCallback(function ($name) {
if($name === 'ldapEmailAttribute') {
return 'myEmailAttribute';
} else {
//for requirement checks
return 'let me pass';
}
}));
$access->expects($this->exactly(3))
->method('combineFilterWithAnd')
->will($this->returnCallback(function ($filterParts) {
return str_replace('=*', '', array_pop($filterParts));
}));
$access->expects($this->exactly(3))
->method('countUsers')
->will($this->returnCallback(function ($filter) {
if($filter === 'myEmailAttribute') {
return 0;
} else if($filter === 'mail') {
return 0;
} else if($filter === 'mailPrimaryAddress') {
return 0;
}
var_dump($filter);
}));
$result = $wizard->detectEmailAttribute();
$this->assertSame(false, $result->hasChanges());
}
public function testCumulativeSearchOnAttributeSkipReadDN() { public function testCumulativeSearchOnAttributeSkipReadDN() {
// tests that there is no infinite loop, when skipping already processed // tests that there is no infinite loop, when skipping already processed
// DNs (they can be returned multiple times for multiple filters ) // DNs (they can be returned multiple times for multiple filters )