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

Populate avatar from SAML attribute #520

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
58c8706
[tx-robot] updated from transifex
nextcloud-bot Mar 22, 2021
45a1b8e
#187 Add avatar_mapping to admin settings
Mar 23, 2021
258b77c
#187 Add german translations for avatar_mapping in admin settings
Mar 23, 2021
354a3ad
#187 Implement getting avatar from publicly available URL with avatar…
Mar 23, 2021
50c7ae6
#187 Adapt test for avatar_mapping in AdminTest
Mar 23, 2021
4e57d60
Remove translation as they are managed in transifex
May 17, 2021
a967f77
Change indentation from 4 spaces to tabs, according to coding guidelines
May 17, 2021
7ef0529
Replace LDAP FilesystemHelper with native methods
May 17, 2021
e03bdfe
Minor refactoring of if statement when checking if a user can change …
May 17, 2021
c448062
Give method to set the avatar from SAML a more speaking name
May 17, 2021
1252b40
[tx-robot] updated from transifex
nextcloud-bot Mar 26, 2021
e0546ef
Bump guzzlehttp/guzzle from 7.0.1 to 7.3.0 in /tests/integration
dependabot-preview[bot] Mar 27, 2021
6caa617
[tx-robot] updated from transifex
nextcloud-bot Mar 31, 2021
c7517bd
[tx-robot] updated from transifex
nextcloud-bot Apr 4, 2021
fa3416d
[tx-robot] updated from transifex
nextcloud-bot Apr 14, 2021
469e600
[tx-robot] updated from transifex
nextcloud-bot Apr 16, 2021
21cffba
[tx-robot] updated from transifex
nextcloud-bot Apr 23, 2021
a659c30
[tx-robot] updated from transifex
nextcloud-bot Apr 24, 2021
4856954
[tx-robot] updated from transifex
nextcloud-bot Apr 27, 2021
98482ae
[tx-robot] updated from transifex
nextcloud-bot Apr 29, 2021
be23456
[tx-robot] updated from transifex
nextcloud-bot Apr 30, 2021
5a60cef
[tx-robot] updated from transifex
nextcloud-bot May 4, 2021
8ca57c8
Allow setting of "retrieveParametersFromServer"
LukasReschke May 4, 2021
8142653
Add new checkbox to AdminTest
LukasReschke May 4, 2021
f1eb5dd
Actually replace $retrieveParametersFromServer parameter
LukasReschke May 5, 2021
d069181
[tx-robot] updated from transifex
nextcloud-bot May 6, 2021
6d96acb
[tx-robot] updated from transifex
nextcloud-bot May 7, 2021
d2ba906
[tx-robot] updated from transifex
nextcloud-bot May 8, 2021
c32ca47
[tx-robot] updated from transifex
nextcloud-bot May 10, 2021
6253cfc
[tx-robot] updated from transifex
nextcloud-bot May 11, 2021
c959239
[tx-robot] updated from transifex
nextcloud-bot May 16, 2021
8caf2d3
Bump version
rullzer May 17, 2021
102cd00
Use dependency injection for avatar manager
May 17, 2021
ebb4413
Only update avatar image if its checksum value has changed
May 17, 2021
e6a3369
merge changes from origin
Aug 26, 2021
fd4c9ae
#187 revert adding german translation as translations are done via tr…
Aug 26, 2021
c622211
#187 rebase from master
Jul 6, 2022
e21126b
fix code style
Jul 6, 2022
7e7584c
fix code style and errors in code
Jul 6, 2022
02d1f10
#187 change avatar config settings for database configuration
Jul 6, 2022
d338a85
Merge branch 'nextcloud:master' into master
Lumrenion Aug 3, 2022
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
2 changes: 1 addition & 1 deletion l10n/de.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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