Merge pull request #6058 from owncloud/ldap2avatar

Set Avatar for LDAP users automatically (if a picture is available)
This commit is contained in:
blizzz 2013-11-26 12:05:32 -08:00
commit 4f15282bc9
14 changed files with 245 additions and 19 deletions

View File

@ -67,16 +67,17 @@ class Group_Proxy extends lib\Proxy implements \OCP\GroupInterface {
* @param $gid string, the gid connected to the request * @param $gid string, the gid connected to the request
* @param $method string, the method of the group backend that shall be called * @param $method string, the method of the group backend that shall be called
* @param $parameters an array of parameters to be passed * @param $parameters an array of parameters to be passed
* @param $passOnWhen the result matches this variable
* @return mixed, the result of the method or false * @return mixed, the result of the method or false
*/ */
protected function callOnLastSeenOn($gid, $method, $parameters) { protected function callOnLastSeenOn($gid, $method, $parameters, $passOnWhen) {
$cacheKey = $this->getGroupCacheKey($gid);; $cacheKey = $this->getGroupCacheKey($gid);;
$prefix = $this->getFromCache($cacheKey); $prefix = $this->getFromCache($cacheKey);
//in case the uid has been found in the past, try this stored connection first //in case the uid has been found in the past, try this stored connection first
if(!is_null($prefix)) { if(!is_null($prefix)) {
if(isset($this->backends[$prefix])) { if(isset($this->backends[$prefix])) {
$result = call_user_func_array(array($this->backends[$prefix], $method), $parameters); $result = call_user_func_array(array($this->backends[$prefix], $method), $parameters);
if(!$result) { if($result === $passOnWhen) {
//not found here, reset cache to null if group vanished //not found here, reset cache to null if group vanished
//because sometimes methods return false with a reason //because sometimes methods return false with a reason
$groupExists = call_user_func_array( $groupExists = call_user_func_array(

View File

@ -199,7 +199,9 @@ class Access extends LDAPUtility {
*/ */
public function username2dn($name) { public function username2dn($name) {
$dn = $this->ocname2dn($name, true); $dn = $this->ocname2dn($name, true);
if($dn) { //Check whether the DN belongs to the Base, to avoid issues on multi-
//server setups
if($dn && $this->isDNPartOfBase($dn, $this->connection->ldapBaseUsers)) {
return $dn; return $dn;
} }

View File

@ -72,6 +72,7 @@ class Configuration {
'ldapExpertUsernameAttr' => null, 'ldapExpertUsernameAttr' => null,
'ldapExpertUUIDUserAttr' => null, 'ldapExpertUUIDUserAttr' => null,
'ldapExpertUUIDGroupAttr' => null, 'ldapExpertUUIDGroupAttr' => null,
'lastJpegPhotoLookup' => null,
); );
public function __construct($configPrefix, $autoread = true) { public function __construct($configPrefix, $autoread = true) {
@ -330,6 +331,7 @@ class Configuration {
'ldap_expert_uuid_user_attr' => '', 'ldap_expert_uuid_user_attr' => '',
'ldap_expert_uuid_group_attr' => '', 'ldap_expert_uuid_group_attr' => '',
'has_memberof_filter_support' => 0, 'has_memberof_filter_support' => 0,
'last_jpegPhoto_lookup' => 0,
); );
} }
@ -377,6 +379,7 @@ class Configuration {
'ldap_expert_uuid_user_attr' => 'ldapExpertUUIDUserAttr', 'ldap_expert_uuid_user_attr' => 'ldapExpertUUIDUserAttr',
'ldap_expert_uuid_group_attr' => 'ldapExpertUUIDGroupAttr', 'ldap_expert_uuid_group_attr' => 'ldapExpertUUIDGroupAttr',
'has_memberof_filter_support' => 'hasMemberOfFilterSupport', 'has_memberof_filter_support' => 'hasMemberOfFilterSupport',
'last_jpegPhoto_lookup' => 'lastJpegPhotoLookup',
); );
return $array; return $array;
} }

View File

@ -54,7 +54,7 @@ abstract class Proxy {
return 'group-'.$gid.'-lastSeenOn'; return 'group-'.$gid.'-lastSeenOn';
} }
abstract protected function callOnLastSeenOn($id, $method, $parameters); abstract protected function callOnLastSeenOn($id, $method, $parameters, $passOnWhen);
abstract protected function walkBackends($id, $method, $parameters); abstract protected function walkBackends($id, $method, $parameters);
/** /**
@ -64,8 +64,9 @@ abstract class Proxy {
* @param $parameters an array of parameters to be passed * @param $parameters an array of parameters to be passed
* @return mixed, the result of the specified method * @return mixed, the result of the specified method
*/ */
protected function handleRequest($id, $method, $parameters) { protected function handleRequest($id, $method, $parameters, $passOnWhen = false) {
if(!$result = $this->callOnLastSeenOn($id, $method, $parameters)) { $result = $this->callOnLastSeenOn($id, $method, $parameters, $passOnWhen);
if($result === $passOnWhen) {
$result = $this->walkBackends($id, $method, $parameters); $result = $this->walkBackends($id, $method, $parameters);
} }
return $result; return $result;

View File

@ -69,6 +69,74 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
} }
} }
/**
* @brief reads jpegPhoto and set is as avatar if available
* @param $uid string ownCloud user name
* @param $dn string the user's LDAP DN
* @return void
*/
private function updateAvatar($uid, $dn) {
$hasLoggedIn = \OCP\Config::getUserValue($uid, 'user_ldap',
'firstLoginAccomplished', 0);
$lastChecked = \OCP\Config::getUserValue($uid, 'user_ldap',
'lastJpegPhotoLookup', 0);
if(($hasLoggedIn !== '1') || (time() - intval($lastChecked)) < 86400 ) {
//update only once a day
return;
}
$jpegPhoto = $this->access->readAttribute($dn, 'jpegPhoto');
\OCP\Config::setUserValue($uid, 'user_ldap', 'lastJpegPhotoLookup', time());
if(!$jpegPhoto || !is_array($jpegPhoto) || !isset($jpegPhoto[0])) {
//not set, nothing left to do;
return;
}
$image = new \OCP\Image();
$image->loadFromBase64(base64_encode($jpegPhoto[0]));
if(!$image->valid()) {
\OCP\Util::writeLog('user_ldap', 'jpegPhoto data invalid for '.$dn,
\OCP\Util::ERROR);
return;
}
//make sure it is a square and not bigger than 128x128
$size = min(array($image->width(), $image->height(), 128));
if(!$image->centerCrop($size)) {
\OCP\Util::writeLog('user_ldap',
'croping image for avatar failed for '.$dn,
\OCP\Util::ERROR);
return;
}
if(!\OC\Files\Filesystem::$loaded) {
\OC_Util::setupFS($uid);
}
$avatarManager = \OC::$server->getAvatarManager();
$avatar = $avatarManager->getAvatar($uid);
$avatar->set($image);
}
/**
* @brief checks whether the user is allowed to change his avatar in ownCloud
* @param $uid string the ownCloud user name
* @return boolean either the user can or cannot
*/
public function canChangeAvatar($uid) {
$dn = $this->access->username2dn($uid);
if(!$dn) {
return false;
}
$jpegPhoto = $this->access->readAttribute($dn, 'jpegPhoto');
if(!$jpegPhoto || !is_array($jpegPhoto) || !isset($jpegPhoto[0])) {
//The user is allowed to change his avatar in ownCloud only if no
//avatar is provided by LDAP
return true;
}
return false;
}
/** /**
* @brief Check if the password is correct * @brief Check if the password is correct
* @param $uid The username * @param $uid The username
@ -100,6 +168,10 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
return false; return false;
} }
\OCP\Config::setUserValue($ocname, 'user_ldap',
'firstLoginAccomplished', 1);
$this->updateAvatar($ocname, $dn);
//give back the display name //give back the display name
return $ocname; return $ocname;
} }
@ -173,6 +245,7 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
$this->access->connection->writeToCache('userExists'.$uid, true); $this->access->connection->writeToCache('userExists'.$uid, true);
$this->updateQuota($dn); $this->updateQuota($dn);
$this->updateAvatar($uid, $dn);
return true; return true;
} }
@ -289,7 +362,8 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
public function implementsActions($actions) { public function implementsActions($actions) {
return (bool)((OC_USER_BACKEND_CHECK_PASSWORD return (bool)((OC_USER_BACKEND_CHECK_PASSWORD
| OC_USER_BACKEND_GET_HOME | OC_USER_BACKEND_GET_HOME
| OC_USER_BACKEND_GET_DISPLAYNAME) | OC_USER_BACKEND_GET_DISPLAYNAME
| OC_USER_BACKEND_PROVIDE_AVATAR)
& $actions); & $actions);
} }

View File

@ -54,6 +54,7 @@ class User_Proxy extends lib\Proxy implements \OCP\UserInterface {
protected function walkBackends($uid, $method, $parameters) { protected function walkBackends($uid, $method, $parameters) {
$cacheKey = $this->getUserCacheKey($uid); $cacheKey = $this->getUserCacheKey($uid);
foreach($this->backends as $configPrefix => $backend) { foreach($this->backends as $configPrefix => $backend) {
// print("walkBackend '$configPrefix'<br/>");
if($result = call_user_func_array(array($backend, $method), $parameters)) { if($result = call_user_func_array(array($backend, $method), $parameters)) {
$this->writeToCache($cacheKey, $configPrefix); $this->writeToCache($cacheKey, $configPrefix);
return $result; return $result;
@ -67,16 +68,17 @@ class User_Proxy extends lib\Proxy implements \OCP\UserInterface {
* @param $uid string, the uid connected to the request * @param $uid string, the uid connected to the request
* @param $method string, the method of the user backend that shall be called * @param $method string, the method of the user backend that shall be called
* @param $parameters an array of parameters to be passed * @param $parameters an array of parameters to be passed
* @param $passOnWhen the result matches this variable
* @return mixed, the result of the method or false * @return mixed, the result of the method or false
*/ */
protected function callOnLastSeenOn($uid, $method, $parameters) { protected function callOnLastSeenOn($uid, $method, $parameters, $passOnWhen) {
$cacheKey = $this->getUserCacheKey($uid); $cacheKey = $this->getUserCacheKey($uid);
$prefix = $this->getFromCache($cacheKey); $prefix = $this->getFromCache($cacheKey);
//in case the uid has been found in the past, try this stored connection first //in case the uid has been found in the past, try this stored connection first
if(!is_null($prefix)) { if(!is_null($prefix)) {
if(isset($this->backends[$prefix])) { if(isset($this->backends[$prefix])) {
$result = call_user_func_array(array($this->backends[$prefix], $method), $parameters); $result = call_user_func_array(array($this->backends[$prefix], $method), $parameters);
if(!$result) { if($result === $passOnWhen) {
//not found here, reset cache to null if user vanished //not found here, reset cache to null if user vanished
//because sometimes methods return false with a reason //because sometimes methods return false with a reason
$userExists = call_user_func_array( $userExists = call_user_func_array(
@ -163,6 +165,15 @@ class User_Proxy extends lib\Proxy implements \OCP\UserInterface {
return $this->handleRequest($uid, 'getDisplayName', array($uid)); return $this->handleRequest($uid, 'getDisplayName', array($uid));
} }
/**
* @brief checks whether the user is allowed to change his avatar in ownCloud
* @param $uid string the ownCloud user name
* @return boolean either the user can or cannot
*/
public function canChangeAvatar($uid) {
return $this->handleRequest($uid, 'canChangeAvatar', array($uid), true);
}
/** /**
* @brief Get a list of all display names * @brief Get a list of all display names
* @returns array with all displayNames (value) and the corresponding uids (key) * @returns array with all displayNames (value) and the corresponding uids (key)

View File

@ -44,15 +44,19 @@ class OC_Avatar implements \OCP\IAvatar {
/** /**
* @brief sets the users avatar * @brief sets the users avatar
* @param $data mixed imagedata or path to set a new avatar * @param $data mixed OC_Image, imagedata or path to set a new avatar
* @throws Exception if the provided file is not a jpg or png image * @throws Exception if the provided file is not a jpg or png image
* @throws Exception if the provided image is not valid * @throws Exception if the provided image is not valid
* @throws \OC\NotSquareException if the image is not square * @throws \OC\NotSquareException if the image is not square
* @return void * @return void
*/ */
public function set ($data) { public function set ($data) {
if($data instanceOf OC_Image) {
$img = new OC_Image($data); $img = $data;
$data = $img->data();
} else {
$img = new OC_Image($data);
}
$type = substr($img->mimeType(), -3); $type = substr($img->mimeType(), -3);
if ($type === 'peg') { if ($type === 'peg') {
$type = 'jpg'; $type = 'jpg';

View File

@ -424,6 +424,22 @@ class OC_User {
} }
} }
/**
* @brief Check whether user can change his avatar
* @param string $uid The username
* @return bool
*
* Check whether a specified user can change his avatar
*/
public static function canUserChangeAvatar($uid) {
$user = self::getManager()->get($uid);
if ($user) {
return $user->canChangeAvatar();
} else {
return false;
}
}
/** /**
* @brief Check whether user can change his password * @brief Check whether user can change his password
* @param string $uid The username * @param string $uid The username

View File

@ -31,13 +31,13 @@ define('OC_USER_BACKEND_NOT_IMPLEMENTED', -501);
/** /**
* actions that user backends can define * actions that user backends can define
*/ */
define('OC_USER_BACKEND_CREATE_USER', 0x000001); define('OC_USER_BACKEND_CREATE_USER', 0x0000001);
define('OC_USER_BACKEND_SET_PASSWORD', 0x000010); define('OC_USER_BACKEND_SET_PASSWORD', 0x0000010);
define('OC_USER_BACKEND_CHECK_PASSWORD', 0x000100); define('OC_USER_BACKEND_CHECK_PASSWORD', 0x0000100);
define('OC_USER_BACKEND_GET_HOME', 0x001000); define('OC_USER_BACKEND_GET_HOME', 0x0001000);
define('OC_USER_BACKEND_GET_DISPLAYNAME', 0x010000); define('OC_USER_BACKEND_GET_DISPLAYNAME', 0x0010000);
define('OC_USER_BACKEND_SET_DISPLAYNAME', 0x100000); define('OC_USER_BACKEND_SET_DISPLAYNAME', 0x0100000);
define('OC_USER_BACKEND_PROVIDE_AVATAR', 0x1000000);
/** /**
* Abstract base class for user management. Provides methods for querying backend * Abstract base class for user management. Provides methods for querying backend
@ -54,6 +54,7 @@ abstract class OC_User_Backend implements OC_User_Interface {
OC_USER_BACKEND_GET_HOME => 'getHome', OC_USER_BACKEND_GET_HOME => 'getHome',
OC_USER_BACKEND_GET_DISPLAYNAME => 'getDisplayName', OC_USER_BACKEND_GET_DISPLAYNAME => 'getDisplayName',
OC_USER_BACKEND_SET_DISPLAYNAME => 'setDisplayName', OC_USER_BACKEND_SET_DISPLAYNAME => 'setDisplayName',
OC_USER_BACKEND_PROVIDE_AVATAR => 'canChangeAvatar',
); );
/** /**

View File

@ -139,6 +139,18 @@ class User {
return \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data") . '/' . $this->uid; //TODO switch to Config object once implemented return \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data") . '/' . $this->uid; //TODO switch to Config object once implemented
} }
/**
* check if the backend allows the user to change his avatar on Personal page
*
* @return bool
*/
public function canChangeAvatar() {
if($this->backend->implementsActions(\OC_USER_BACKEND_PROVIDE_AVATAR)) {
return $this->backend->canChangeAvatar($this->uid);
}
return true;
}
/** /**
* check if the backend supports changing passwords * check if the backend supports changing passwords
* *

View File

@ -90,6 +90,7 @@ $tmpl->assign('displayNameChangeSupported', OC_User::canUserChangeDisplayName(OC
$tmpl->assign('displayName', OC_User::getDisplayName()); $tmpl->assign('displayName', OC_User::getDisplayName());
$tmpl->assign('enableDecryptAll' , $enableDecryptAll); $tmpl->assign('enableDecryptAll' , $enableDecryptAll);
$tmpl->assign('enableAvatars', \OC_Config::getValue('enable_avatars', true)); $tmpl->assign('enableAvatars', \OC_Config::getValue('enable_avatars', true));
$tmpl->assign('avatarChangeSupported', OC_User::canUserChangeAvatar(OC_User::getUser()));
$forms=OC_App::getForms('personal'); $forms=OC_App::getForms('personal');
$tmpl->assign('forms', array()); $tmpl->assign('forms', array());

View File

@ -87,11 +87,15 @@ if($_['passwordChangeSupported']) {
<div id="displayavatar"> <div id="displayavatar">
<div class="avatardiv"></div><br> <div class="avatardiv"></div><br>
<div class="warning hidden"></div> <div class="warning hidden"></div>
<?php if ($_['avatarChangeSupported']): ?>
<div class="inlineblock button" id="uploadavatarbutton"><?php p($l->t('Upload new')); ?></div> <div class="inlineblock button" id="uploadavatarbutton"><?php p($l->t('Upload new')); ?></div>
<input type="file" class="hidden" name="files[]" id="uploadavatar"> <input type="file" class="hidden" name="files[]" id="uploadavatar">
<div class="inlineblock button" id="selectavatar"><?php p($l->t('Select new from Files')); ?></div> <div class="inlineblock button" id="selectavatar"><?php p($l->t('Select new from Files')); ?></div>
<div class="inlineblock button" id="removeavatar"><?php p($l->t('Remove image')); ?></div><br> <div class="inlineblock button" id="removeavatar"><?php p($l->t('Remove image')); ?></div><br>
<?php p($l->t('Either png or jpg. Ideally square but you will be able to crop it.')); ?> <?php p($l->t('Either png or jpg. Ideally square but you will be able to crop it.')); ?>
<?php else: ?>
<?php p($l->t('Your avatar is provided by your original account.')); ?>
<?php endif; ?>
</div> </div>
<div id="cropper" class="hidden"> <div id="cropper" class="hidden">
<div class="inlineblock button" id="abortcropperbutton"><?php p($l->t('Abort')); ?></div> <div class="inlineblock button" id="abortcropperbutton"><?php p($l->t('Abort')); ?></div>

View File

@ -0,0 +1,27 @@
<?php
/**
* ownCloud
*
* @author Arthur Schiwon
* @copyright 2013 Arthur Schiwon blizzz@owncloud.com
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
class Avatar_User_Dummy extends \OC_User_Dummy {
public function canChangeAvatar($uid) {
return true;
}
}

View File

@ -87,6 +87,75 @@ class User extends \PHPUnit_Framework_TestCase {
$this->assertFalse($user->setPassword('bar','')); $this->assertFalse($user->setPassword('bar',''));
} }
public function testChangeAvatarSupportedYes() {
/**
* @var \OC_User_Backend | \PHPUnit_Framework_MockObject_MockObject $backend
*/
require_once 'avataruserdummy.php';
$backend = $this->getMock('Avatar_User_Dummy');
$backend->expects($this->once())
->method('canChangeAvatar')
->with($this->equalTo('foo'))
->will($this->returnValue(true));
$backend->expects($this->any())
->method('implementsActions')
->will($this->returnCallback(function ($actions) {
if ($actions === \OC_USER_BACKEND_PROVIDE_AVATAR) {
return true;
} else {
return false;
}
}));
$user = new \OC\User\User('foo', $backend);
$this->assertTrue($user->canChangeAvatar());
}
public function testChangeAvatarSupportedNo() {
/**
* @var \OC_User_Backend | \PHPUnit_Framework_MockObject_MockObject $backend
*/
require_once 'avataruserdummy.php';
$backend = $this->getMock('Avatar_User_Dummy');
$backend->expects($this->once())
->method('canChangeAvatar')
->with($this->equalTo('foo'))
->will($this->returnValue(false));
$backend->expects($this->any())
->method('implementsActions')
->will($this->returnCallback(function ($actions) {
if ($actions === \OC_USER_BACKEND_PROVIDE_AVATAR) {
return true;
} else {
return false;
}
}));
$user = new \OC\User\User('foo', $backend);
$this->assertFalse($user->canChangeAvatar());
}
public function testChangeAvatarNotSupported() {
/**
* @var \OC_User_Backend | \PHPUnit_Framework_MockObject_MockObject $backend
*/
require_once 'avataruserdummy.php';
$backend = $this->getMock('Avatar_User_Dummy');
$backend->expects($this->never())
->method('canChangeAvatar');
$backend->expects($this->any())
->method('implementsActions')
->will($this->returnCallback(function ($actions) {
return false;
}));
$user = new \OC\User\User('foo', $backend);
$this->assertTrue($user->canChangeAvatar());
}
public function testDelete() { public function testDelete() {
/** /**
* @var \OC_User_Backend | \PHPUnit_Framework_MockObject_MockObject $backend * @var \OC_User_Backend | \PHPUnit_Framework_MockObject_MockObject $backend