- Reduced options to single new configuration paramter instead of two.
- Moved more logic into UserAvatars class.
- Updated LDAP avatar import to also run on login when no image is
currently set.
- Added thumbnail fetching to search requests.
- Added testing to cover.
Related to PR #2320, and issue #1161
$this->ldapService->syncGroups($user, $username);
}
+ // Attach avatar if non-existent
+ if (is_null($user->avatar)) {
+ $this->ldapService->saveAndAttachAvatar($user, $userDetails);
+ }
+
$this->login($user, $remember);
return true;
}
];
$user = $this->registrationService->registerUser($details, null, false);
-
- if (config('services.ldap.import_thumbnail_photos')) {
- $imageService = app()->make(ImageService::class);
- $image = $imageService->saveNewFromBase64Uri('data:image/jpg;base64,'.base64_encode($ldapUserDetails['avatar']), $ldapUserDetails['uid'].'.jpg', 'user');
-
- $user['image_id'] = $image->id;
- $user->save();
- }
-
+ $this->ldapService->saveAndAttachAvatar($user, $ldapUserDetails);
return $user;
}
}
use BookStack\Auth\User;
use BookStack\Exceptions\JsonDebugException;
use BookStack\Exceptions\LdapException;
+use BookStack\Uploads\UserAvatars;
use ErrorException;
+use Illuminate\Support\Facades\Log;
/**
* Class LdapService
protected $ldap;
protected $ldapConnection;
+ protected $userAvatars;
protected $config;
protected $enabled;
/**
* LdapService constructor.
*/
- public function __construct(Ldap $ldap)
+ public function __construct(Ldap $ldap, UserAvatars $userAvatars)
{
$this->ldap = $ldap;
+ $this->userAvatars = $userAvatars;
$this->config = config('services.ldap');
$this->enabled = config('auth.method') === 'ldap';
}
$displayNameAttr = $this->config['display_name_attribute'];
$thumbnailAttr = $this->config['thumbnail_attribute'];
- $user = $this->getUserWithAttributes($userName, ['cn', 'dn', $idAttr, $emailAttr, $displayNameAttr]);
+ $user = $this->getUserWithAttributes($userName, array_filter([
+ 'cn', 'dn', $idAttr, $emailAttr, $displayNameAttr, $thumbnailAttr,
+ ]));
- if ($user === null) {
+ if (is_null($user)) {
return null;
}
'name' => $this->getUserResponseProperty($user, $displayNameAttr, $userCn),
'dn' => $user['dn'],
'email' => $this->getUserResponseProperty($user, $emailAttr, null),
- 'avatar'=> $this->getUserResponseProperty($user, $thumbnailAttr, null),
+ 'avatar'=> $thumbnailAttr ? $this->getUserResponseProperty($user, $thumbnailAttr, null) : null,
];
if ($this->config['dump_user_details']) {
$userLdapGroups = $this->getUserGroups($username);
$this->syncWithGroups($user, $userLdapGroups);
}
+
+ /**
+ * Save and attach an avatar image, if found in the ldap details, and attach
+ * to the given user model.
+ */
+ public function saveAndAttachAvatar(User $user, array $ldapUserDetails): void
+ {
+ if (is_null(config('services.ldap.thumbnail_attribute')) || is_null($ldapUserDetails['avatar'])) {
+ return;
+ }
+
+ try {
+ $imageData = $ldapUserDetails['avatar'];
+ $this->userAvatars->assignToUserFromExistingData($user, $imageData, 'jpg');
+ } catch (\Exception $exception) {
+ Log::info("Failed to use avatar image from LDAP data for user id {$user->id}");
+ }
+ }
}
use BookStack\Entities\Models\Page;
use BookStack\Exceptions\NotFoundException;
use BookStack\Exceptions\UserUpdateException;
-use BookStack\Uploads\Image;
use BookStack\Uploads\UserAvatars;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Pagination\LengthAwarePaginator;
-use Images;
use Log;
class UserRepo
$user->delete();
// Delete user profile images
- $profileImages = Image::query()->where('type', '=', 'user')
- ->where('uploaded_to', '=', $user->id)
- ->get();
-
- foreach ($profileImages as $image) {
- Images::destroy($image);
- }
+ $this->userAvatar->destroyAllForUser($user);
if (!empty($newOwnerId)) {
$newOwner = User::query()->find($newOwnerId);
// Custom BookStack
'Activity' => BookStack\Facades\Activity::class,
- 'Images' => BookStack\Facades\Images::class,
'Permissions' => BookStack\Facades\Permissions::class,
'Theme' => BookStack\Facades\Theme::class,
],
'remove_from_groups' => env('LDAP_REMOVE_FROM_GROUPS', false),
'tls_insecure' => env('LDAP_TLS_INSECURE', false),
'start_tls' => env('LDAP_START_TLS', false),
- 'import_thumbnail_photos' => env('LDAP_IMPORT_THUMBNAIL_PHOTOS', false),
- 'thumbnail_attribute' => env('LDAP_THUMBNAIL_ATTRIBUTE', 'thumbnailPhoto'),
+ 'thumbnail_attribute' => env('LDAP_THUMBNAIL_ATTRIBUTE', null),
],
];
+++ /dev/null
-<?php namespace BookStack\Facades;
-
-use Illuminate\Support\Facades\Facade;
-
-class Images extends Facade
-{
- /**
- * Get the registered name of the component.
- *
- * @return string
- */
- protected static function getFacadeAccessor()
- {
- return 'images';
- }
-}
use BookStack\Entities\Models\Page;
use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater;
-use Images;
class Image extends Model
{
/**
* Get a thumbnail for this image.
- * @param int $width
- * @param int $height
- * @param bool|false $keepRatio
- * @return string
* @throws \Exception
*/
- public function getThumb($width, $height, $keepRatio = false)
+ public function getThumb(int $width, int $height, bool $keepRatio = false): string
{
- return Images::getThumbnail($this, $width, $height, $keepRatio);
+ return app()->make(ImageService::class)->getThumbnail($this, $width, $height, $keepRatio);
}
/**
* Get the page this image has been uploaded to.
* Only applicable to gallery or drawio image types.
- * @return Page|null
*/
- public function getPage()
+ public function getPage(): ?Page
{
return $this->belongsTo(Page::class, 'uploaded_to')->first();
}
}
try {
+ $this->destroyAllForUser($user);
$avatar = $this->saveAvatarImage($user);
$user->avatar()->associate($avatar);
$user->save();
}
}
+ /**
+ * Assign a new avatar image to the given user using the given image data.
+ */
+ public function assignToUserFromExistingData(User $user, string $imageData, string $extension): void
+ {
+ try {
+ $this->destroyAllForUser($user);
+ $avatar = $this->createAvatarImageFromData($user, $imageData, $extension);
+ $user->avatar()->associate($avatar);
+ $user->save();
+ } catch (Exception $e) {
+ Log::error('Failed to save user avatar image');
+ }
+ }
+
+ /**
+ * Destroy all user avatars uploaded to the given user.
+ */
+ public function destroyAllForUser(User $user)
+ {
+ $profileImages = Image::query()->where('type', '=', 'user')
+ ->where('uploaded_to', '=', $user->id)
+ ->get();
+
+ foreach ($profileImages as $image) {
+ $this->imageService->destroy($image);
+ }
+ }
+
/**
* Save an avatar image from an external service.
* @throws Exception
];
$userAvatarUrl = strtr($avatarUrl, $replacements);
- $imageName = str_replace(' ', '-', $user->id . '-avatar.png');
$imageData = $this->getAvatarImageData($userAvatarUrl);
+ return $this->createAvatarImageFromData($user, $imageData, 'png');
+ }
+
+ /**
+ * Creates a new image instance and saves it in the system as a new user avatar image.
+ */
+ protected function createAvatarImageFromData(User $user, string $imageData, string $extension): Image
+ {
+ $imageName = str_replace(' ', '-', $user->id . '-avatar.' . $extension);
$image = $this->imageService->saveNew($imageName, $imageData, 'user', $user->id);
$image->created_by = $user->id;
use BookStack\Auth\Role;
use BookStack\Auth\Access\Ldap;
use BookStack\Auth\User;
-use BookStack\Exceptions\LdapException;
use Mockery\MockInterface;
use Tests\BrowserKitTest;
'services.ldap.user_filter' => '(&(uid=${user}))',
'services.ldap.follow_referrals' => false,
'services.ldap.tls_insecure' => false,
+ 'services.ldap.thumbnail_attribute' => null,
]);
$this->mockLdap = \Mockery::mock(Ldap::class);
$this->app[Ldap::class] = $this->mockLdap;
$this->runFailedAuthLogin();
$this->assertTrue($log->hasWarningThatContains('Failed login for timmyjenkins'));
}
+
+ public function test_thumbnail_attribute_used_as_user_avatar_if_configured()
+ {
+ config()->set(['services.ldap.thumbnail_attribute' => 'jpegPhoto']);
+
+ $this->commonLdapMocks(1, 1, 1, 2, 1);
+ $ldapDn = 'cn=test-user,dc=test' . config('services.ldap.base_dn');
+ $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
+ ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
+ ->andReturn(['count' => 1, 0 => [
+ 'cn' => [$this->mockUser->name],
+ 'dn' => $ldapDn,
+ 'jpegphoto' => [base64_decode('/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8Q
+EBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=')],
+ 'mail' => [$this->mockUser->email]
+ ]]);
+
+ $this->mockUserLogin()
+ ->seePageIs('/');
+
+ $user = User::query()->where('email', '=' , $this->mockUser->email)->first();
+ $this->assertNotNull($user->avatar);
+ $this->assertEquals('8c90748342f19b195b9c6b4eff742ded', md5_file(public_path($user->avatar->path)));
+ }
}