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 $method string, the method of the group backend that shall be called
* @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
*/
protected function callOnLastSeenOn($gid, $method, $parameters) {
protected function callOnLastSeenOn($gid, $method, $parameters, $passOnWhen) {
$cacheKey = $this->getGroupCacheKey($gid);;
$prefix = $this->getFromCache($cacheKey);
//in case the uid has been found in the past, try this stored connection first
if(!is_null($prefix)) {
if(isset($this->backends[$prefix])) {
$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
//because sometimes methods return false with a reason
$groupExists = call_user_func_array(

View File

@ -199,7 +199,9 @@ class Access extends LDAPUtility {
*/
public function username2dn($name) {
$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;
}

View File

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

View File

@ -54,7 +54,7 @@ abstract class Proxy {
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);
/**
@ -64,8 +64,9 @@ abstract class Proxy {
* @param $parameters an array of parameters to be passed
* @return mixed, the result of the specified method
*/
protected function handleRequest($id, $method, $parameters) {
if(!$result = $this->callOnLastSeenOn($id, $method, $parameters)) {
protected function handleRequest($id, $method, $parameters, $passOnWhen = false) {
$result = $this->callOnLastSeenOn($id, $method, $parameters, $passOnWhen);
if($result === $passOnWhen) {
$result = $this->walkBackends($id, $method, $parameters);
}
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
* @param $uid The username
@ -100,6 +168,10 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
return false;
}
\OCP\Config::setUserValue($ocname, 'user_ldap',
'firstLoginAccomplished', 1);
$this->updateAvatar($ocname, $dn);
//give back the display name
return $ocname;
}
@ -173,6 +245,7 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
$this->access->connection->writeToCache('userExists'.$uid, true);
$this->updateQuota($dn);
$this->updateAvatar($uid, $dn);
return true;
}
@ -289,7 +362,8 @@ class USER_LDAP extends BackendUtility implements \OCP\UserInterface {
public function implementsActions($actions) {
return (bool)((OC_USER_BACKEND_CHECK_PASSWORD
| OC_USER_BACKEND_GET_HOME
| OC_USER_BACKEND_GET_DISPLAYNAME)
| OC_USER_BACKEND_GET_DISPLAYNAME
| OC_USER_BACKEND_PROVIDE_AVATAR)
& $actions);
}

View File

@ -54,6 +54,7 @@ class User_Proxy extends lib\Proxy implements \OCP\UserInterface {
protected function walkBackends($uid, $method, $parameters) {
$cacheKey = $this->getUserCacheKey($uid);
foreach($this->backends as $configPrefix => $backend) {
// print("walkBackend '$configPrefix'<br/>");
if($result = call_user_func_array(array($backend, $method), $parameters)) {
$this->writeToCache($cacheKey, $configPrefix);
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 $method string, the method of the user backend that shall be called
* @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
*/
protected function callOnLastSeenOn($uid, $method, $parameters) {
protected function callOnLastSeenOn($uid, $method, $parameters, $passOnWhen) {
$cacheKey = $this->getUserCacheKey($uid);
$prefix = $this->getFromCache($cacheKey);
//in case the uid has been found in the past, try this stored connection first
if(!is_null($prefix)) {
if(isset($this->backends[$prefix])) {
$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
//because sometimes methods return false with a reason
$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));
}
/**
* @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
* @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
* @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 image is not valid
* @throws \OC\NotSquareException if the image is not square
* @return void
*/
public function set ($data) {
$img = new OC_Image($data);
if($data instanceOf OC_Image) {
$img = $data;
$data = $img->data();
} else {
$img = new OC_Image($data);
}
$type = substr($img->mimeType(), -3);
if ($type === 'peg') {
$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
* @param string $uid The username

View File

@ -31,13 +31,13 @@ define('OC_USER_BACKEND_NOT_IMPLEMENTED', -501);
/**
* actions that user backends can define
*/
define('OC_USER_BACKEND_CREATE_USER', 0x000001);
define('OC_USER_BACKEND_SET_PASSWORD', 0x000010);
define('OC_USER_BACKEND_CHECK_PASSWORD', 0x000100);
define('OC_USER_BACKEND_GET_HOME', 0x001000);
define('OC_USER_BACKEND_GET_DISPLAYNAME', 0x010000);
define('OC_USER_BACKEND_SET_DISPLAYNAME', 0x100000);
define('OC_USER_BACKEND_CREATE_USER', 0x0000001);
define('OC_USER_BACKEND_SET_PASSWORD', 0x0000010);
define('OC_USER_BACKEND_CHECK_PASSWORD', 0x0000100);
define('OC_USER_BACKEND_GET_HOME', 0x0001000);
define('OC_USER_BACKEND_GET_DISPLAYNAME', 0x0010000);
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
@ -54,6 +54,7 @@ abstract class OC_User_Backend implements OC_User_Interface {
OC_USER_BACKEND_GET_HOME => 'getHome',
OC_USER_BACKEND_GET_DISPLAYNAME => 'getDisplayName',
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
}
/**
* 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
*

View File

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

View File

@ -87,11 +87,15 @@ if($_['passwordChangeSupported']) {
<div id="displayavatar">
<div class="avatardiv"></div><br>
<div class="warning hidden"></div>
<?php if ($_['avatarChangeSupported']): ?>
<div class="inlineblock button" id="uploadavatarbutton"><?php p($l->t('Upload new')); ?></div>
<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="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 else: ?>
<?php p($l->t('Your avatar is provided by your original account.')); ?>
<?php endif; ?>
</div>
<div id="cropper" class="hidden">
<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',''));
}
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() {
/**
* @var \OC_User_Backend | \PHPUnit_Framework_MockObject_MockObject $backend