Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#187 Populate avatar from SAML attribute #640

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions appinfo/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
\OC::$server->getLogger(),
$userData,
\OC::$server->query(\OCP\EventDispatcher\IEventDispatcher::class),
\OC::$server->getAvatarManager(),
);
$userBackend->registerBackends(\OC::$server->getUserManager()->getBackends());
OC_User::useBackend($userBackend);
Expand Down
1 change: 1 addition & 0 deletions lib/SAMLSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class SAMLSettings {
'saml-attribute-mapping-group_mapping',
'saml-attribute-mapping-home_mapping',
'saml-attribute-mapping-quota_mapping',
'saml-attribute-mapping-avatar_mapping',
'sp-x509cert',
'sp-name-id-format',
'sp-privateKey',
Expand Down
5 changes: 5 additions & 0 deletions lib/Settings/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ public function getForm() {
'type' => 'line',
'required' => true,
],
'avatar_mapping' => [
'text' => $this->l10n->t('Attribute to map the users avatar to.'),
'type' => 'line',
'required' => true,
],

];

Expand Down
83 changes: 82 additions & 1 deletion lib/UserBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@

namespace OCA\User_SAML;

use OC\Files\Filesystem;
use OC\User\Backend;
use OCP\Authentication\IApacheBackend;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\NotPermittedException;
use OCP\IAvatarManager;
use OCP\IDBConnection;
use OCP\ILogger;
use OCP\IUser;
Expand Down Expand Up @@ -61,6 +64,8 @@ class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
private $userData;
/** @var IEventDispatcher */
private $eventDispatcher;
/** @var IAvatarManager */
private $avatarManager;

public function __construct(
IConfig $config,
Expand All @@ -72,7 +77,8 @@ public function __construct(
SAMLSettings $settings,
ILogger $logger,
UserData $userData,
IEventDispatcher $eventDispatcher
IEventDispatcher $eventDispatcher,
IAvatarManager $avatarManager
) {
$this->config = $config;
$this->urlGenerator = $urlGenerator;
Expand All @@ -84,6 +90,25 @@ public function __construct(
$this->logger = $logger;
$this->userData = $userData;
$this->eventDispatcher = $eventDispatcher;
$this->avatarManager = $avatarManager;
}

/**
* checks whether the user is allowed to change his avatar in Nextcloud
*
* @param string $uid the Nextcloud user name
* @return boolean either the user can or cannot
* @throws \Exception
*/
public function canChangeAvatar($uid) {
if (!$this->implementsActions(Backend::PROVIDE_AVATAR)) {
return true;
}
try {
return empty(trim($this->getAttributeKeys('saml-attribute-mapping-avatar_mapping')[0]));
} catch (\InvalidArgumentException $e) {
return true;
}
}

/**
Expand Down Expand Up @@ -185,6 +210,7 @@ public function implementsActions($actions) {
$availableActions |= \OC\User\Backend::GET_DISPLAYNAME;
$availableActions |= \OC\User\Backend::GET_HOME;
$availableActions |= \OC\User\Backend::COUNT_USERS;
$availableActions |= \OC\User\Backend::PROVIDE_AVATAR;
return (bool)($availableActions & $actions);
}

Expand Down Expand Up @@ -642,6 +668,14 @@ public function updateAttributes($uid,
$newGroups = null;
}

try {
$newAvatar = $this->getAttributeValue('saml-attribute-mapping-avatar_mapping', $attributes);
$this->logger->debug('Avatar attribute content: {avatar}', ['app' => 'user_saml', 'avatar' => $newAvatar]);
} catch (\InvalidArgumentException $e) {
$this->logger->debug('Failed to fetch avatar attribute: {exception}', ['app' => 'user_saml', 'exception' => $e->getMessage()]);
$newAvatar = null;
}

if ($user !== null) {
$currentEmail = (string)(method_exists($user, 'getSystemEMailAddress') ? $user->getSystemEMailAddress() : $user->getEMailAddress());
if ($newEmail !== null
Expand Down Expand Up @@ -677,7 +711,54 @@ public function updateAttributes($uid,
$groupManager->get($group)->removeUser($user);
}
}

if ($newAvatar !== null) {
$image = new \OCP\Image();
$fileData = file_get_contents($newAvatar);
$image->loadFromData($fileData);

$checksum = md5($image->data());
if ($checksum !== $this->config->getUserValue($uid, 'user_saml', 'lastAvatarChecksum')) {
// use the checksum before modifications
if ($this->setAvatarFromSamlProvider($uid, $image)) {
// save checksum only after successful setting
$this->config->setUserValue($uid, 'user_saml', 'lastAvatarChecksum', $checksum);
}
}
}
}
}

private function setAvatarFromSamlProvider($uid, $image) {
if (!$image->valid()) {
$this->logger->debug('avatar image data from LDAP invalid for ' . $uid);
return false;
}


//make sure it is a square and not bigger than 128x128
$size = min([$image->width(), $image->height(), 128]);
if (!$image->centerCrop($size)) {
$this->logger->debug('croping image for avatar failed for ' . $uid);
return false;
}

if (!Filesystem::$loaded) {
\OC_Util::setupFS($uid);
}

try {
$avatar = $this->avatarManager->getAvatar($uid);
$avatar->set($image);
return true;
} catch (\Exception $e) {
$this->logger->logException($e, [
'message' => 'Could not set avatar for ' . $uid,
'level' => ILogger::INFO,
'app' => 'user_saml',
]);
}
return false;
}


Expand Down
5 changes: 5 additions & 0 deletions tests/unit/Settings/AdminTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ public function formDataProvider() {
'type' => 'line',
'required' => true,
],
'avatar_mapping' => [
'text' => $this->l10n->t('Attribute to map the users avatar to.'),
'type' => 'line',
'required' => true,
],
];

$nameIdFormats = [
Expand Down
10 changes: 8 additions & 2 deletions tests/unit/UserBackendTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use OCA\User_SAML\UserBackend;
use OCA\User_SAML\UserData;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IAvatarManager;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroup;
Expand Down Expand Up @@ -61,6 +62,8 @@ class UserBackendTest extends TestCase {
private $logger;
/** @var IEventDispatcher|MockObject */
private $eventDispatcher;
/** @var IAvatarManager|MockObject */
private $avatarManager;

protected function setUp(): void {
parent::setUp();
Expand All @@ -75,6 +78,7 @@ protected function setUp(): void {
$this->logger = $this->createMock(ILogger::class);
$this->userData = $this->createMock(UserData::class);
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->avatarManager = $this->createMock(IAvatarManager::class);
}

public function getMockedBuilder(array $mockedFunctions = []) {
Expand All @@ -90,7 +94,8 @@ public function getMockedBuilder(array $mockedFunctions = []) {
$this->SAMLSettings,
$this->logger,
$this->userData,
$this->eventDispatcher
$this->eventDispatcher,
$this->avatarManager
])
->setMethods($mockedFunctions)
->getMock();
Expand All @@ -105,7 +110,8 @@ public function getMockedBuilder(array $mockedFunctions = []) {
$this->SAMLSettings,
$this->logger,
$this->userData,
$this->eventDispatcher
$this->eventDispatcher,
$this->avatarManager
);
}
}
Expand Down