Skip to content

Commit cd7bec6

Browse files
Populate avatar from SAML attribute
Signed-off-by: Rick Bruyninckx <[email protected]>
1 parent 744bdea commit cd7bec6

File tree

5 files changed

+102
-4
lines changed

5 files changed

+102
-4
lines changed

appinfo/app.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
\OC::$server->getLogger(),
5959
$userData,
6060
\OC::$server->query(\OCP\EventDispatcher\IEventDispatcher::class),
61+
\OC::$server->getAvatarManager(),
6162
);
6263
$userBackend->registerBackends(\OC::$server->getUserManager()->getBackends());
6364
OC_User::useBackend($userBackend);

lib/Settings/Admin.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ public function getForm() {
140140
'type' => 'line',
141141
'required' => false,
142142
],
143+
'avatar_mapping' => [
144+
'text' => $this->l10n->t('Attribute to map the users avatar to.'),
145+
'type' => 'line',
146+
'required' => true,
147+
],
143148

144149
];
145150

lib/UserBackend.php

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@
2121

2222
namespace OCA\User_SAML;
2323

24+
use OC\Files\Filesystem;
25+
use OC\User\Backend;
2426
use OCP\Authentication\IApacheBackend;
2527
use OCP\DB\QueryBuilder\IQueryBuilder;
2628
use OCP\EventDispatcher\IEventDispatcher;
2729
use OCP\Files\NotPermittedException;
30+
use OCP\IAvatarManager;
2831
use OCP\IConfig;
2932
use OCP\IDBConnection;
3033
use OCP\IGroupManager;
@@ -61,6 +64,8 @@ class UserBackend implements IApacheBackend, UserInterface, IUserBackend {
6164
private $userData;
6265
/** @var IEventDispatcher */
6366
private $eventDispatcher;
67+
/** @var IAvatarManager */
68+
private $avatarManager;
6469

6570
public function __construct(
6671
IConfig $config,
@@ -72,8 +77,9 @@ public function __construct(
7277
SAMLSettings $settings,
7378
ILogger $logger,
7479
UserData $userData,
75-
IEventDispatcher $eventDispatcher
76-
) {
80+
IEventDispatcher $eventDispatcher,
81+
IAvatarManager $avatarManager
82+
) {
7783
$this->config = $config;
7884
$this->urlGenerator = $urlGenerator;
7985
$this->session = $session;
@@ -84,6 +90,25 @@ public function __construct(
8490
$this->logger = $logger;
8591
$this->userData = $userData;
8692
$this->eventDispatcher = $eventDispatcher;
93+
$this->avatarManager = $avatarManager;
94+
}
95+
96+
/**
97+
* checks whether the user is allowed to change his avatar in Nextcloud
98+
*
99+
* @param string $uid the Nextcloud user name
100+
* @return boolean either the user can or cannot
101+
* @throws \Exception
102+
*/
103+
public function canChangeAvatar($uid) {
104+
if (!$this->implementsActions(Backend::PROVIDE_AVATAR)) {
105+
return true;
106+
}
107+
try {
108+
return empty(trim($this->getAttributeKeys('saml-attribute-mapping-avatar_mapping')[0]));
109+
} catch (\InvalidArgumentException $e) {
110+
return true;
111+
}
87112
}
88113

89114
/**
@@ -185,6 +210,7 @@ public function implementsActions($actions) {
185210
$availableActions |= \OC\User\Backend::GET_DISPLAYNAME;
186211
$availableActions |= \OC\User\Backend::GET_HOME;
187212
$availableActions |= \OC\User\Backend::COUNT_USERS;
213+
$availableActions |= \OC\User\Backend::PROVIDE_AVATAR;
188214
return (bool)($availableActions & $actions);
189215
}
190216

@@ -648,6 +674,14 @@ public function updateAttributes($uid,
648674
$newGroups = null;
649675
}
650676

677+
try {
678+
$newAvatar = $this->getAttributeValue('saml-attribute-mapping-avatar_mapping', $attributes);
679+
$this->logger->debug('Avatar attribute content: {avatar}', ['app' => 'user_saml', 'avatar' => $newAvatar]);
680+
} catch (\InvalidArgumentException $e) {
681+
$this->logger->debug('Failed to fetch avatar attribute: {exception}', ['app' => 'user_saml', 'exception' => $e->getMessage()]);
682+
$newAvatar = null;
683+
}
684+
651685
if ($user !== null) {
652686
$currentEmail = (string)(method_exists($user, 'getSystemEMailAddress') ? $user->getSystemEMailAddress() : $user->getEMailAddress());
653687
if ($newEmail !== null
@@ -683,7 +717,54 @@ public function updateAttributes($uid,
683717
$groupManager->get($group)->removeUser($user);
684718
}
685719
}
720+
721+
if ($newAvatar !== null) {
722+
$image = new \OCP\Image();
723+
$fileData = file_get_contents($newAvatar);
724+
$image->loadFromData($fileData);
725+
726+
$checksum = md5($image->data());
727+
if ($checksum !== $this->config->getUserValue($uid, 'user_saml', 'lastAvatarChecksum')) {
728+
// use the checksum before modifications
729+
if ($this->setAvatarFromSamlProvider($uid, $image)) {
730+
// save checksum only after successful setting
731+
$this->config->setUserValue($uid, 'user_saml', 'lastAvatarChecksum', $checksum);
732+
}
733+
}
734+
}
735+
}
736+
}
737+
738+
private function setAvatarFromSamlProvider($uid, $image) {
739+
if (!$image->valid()) {
740+
$this->logger->debug('avatar image data from LDAP invalid for ' . $uid);
741+
return false;
742+
}
743+
744+
745+
//make sure it is a square and not bigger than 128x128
746+
$size = min([$image->width(), $image->height(), 128]);
747+
if (!$image->centerCrop($size)) {
748+
$this->logger->debug('croping image for avatar failed for ' . $uid);
749+
return false;
750+
}
751+
752+
if (!Filesystem::$loaded) {
753+
\OC_Util::setupFS($uid);
754+
}
755+
756+
try {
757+
$avatar = $this->avatarManager->getAvatar($uid);
758+
$avatar->set($image);
759+
return true;
760+
} catch (\Exception $e) {
761+
$this->logger->logException($e, [
762+
'message' => 'Could not set avatar for ' . $uid,
763+
'level' => ILogger::INFO,
764+
'app' => 'user_saml',
765+
]);
686766
}
767+
return false;
687768
}
688769

689770

tests/unit/Settings/AdminTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ public function formDataProvider() {
152152
'type' => 'line',
153153
'required' => false,
154154
],
155+
'avatar_mapping' => [
156+
'text' => $this->l10n->t('Attribute to map the users avatar to.'),
157+
'type' => 'line',
158+
'required' => true,
159+
],
155160
];
156161

157162
$userFilterSettings = [

tests/unit/UserBackendTest.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use OCA\User_SAML\UserBackend;
2626
use OCA\User_SAML\UserData;
2727
use OCP\EventDispatcher\IEventDispatcher;
28+
use OCP\IAvatarManager;
2829
use OCP\IConfig;
2930
use OCP\IDBConnection;
3031
use OCP\IGroup;
@@ -61,6 +62,8 @@ class UserBackendTest extends TestCase {
6162
private $logger;
6263
/** @var IEventDispatcher|MockObject */
6364
private $eventDispatcher;
65+
/** @var IAvatarManager|MockObject */
66+
private $avatarManager;
6467

6568
protected function setUp(): void {
6669
parent::setUp();
@@ -75,6 +78,7 @@ protected function setUp(): void {
7578
$this->logger = $this->createMock(ILogger::class);
7679
$this->userData = $this->createMock(UserData::class);
7780
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
81+
$this->avatarManager = $this->createMock(IAvatarManager::class);
7882
}
7983

8084
public function getMockedBuilder(array $mockedFunctions = []) {
@@ -90,7 +94,8 @@ public function getMockedBuilder(array $mockedFunctions = []) {
9094
$this->SAMLSettings,
9195
$this->logger,
9296
$this->userData,
93-
$this->eventDispatcher
97+
$this->eventDispatcher,
98+
$this->avatarManager
9499
])
95100
->setMethods($mockedFunctions)
96101
->getMock();
@@ -105,7 +110,8 @@ public function getMockedBuilder(array $mockedFunctions = []) {
105110
$this->SAMLSettings,
106111
$this->logger,
107112
$this->userData,
108-
$this->eventDispatcher
113+
$this->eventDispatcher,
114+
$this->avatarManager
109115
);
110116
}
111117
}

0 commit comments

Comments
 (0)