summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog1
-rw-r--r--app/Auth/LdapAuth.php2
-rw-r--r--app/Controller/AvatarFile.php2
-rw-r--r--app/Core/Ldap/User.php15
-rw-r--r--app/Core/User/UserProfile.php4
-rw-r--r--app/Core/User/UserSync.php6
-rw-r--r--app/Event/UserProfileSyncEvent.php64
-rw-r--r--app/Model/AvatarFile.php32
-rw-r--r--app/ServiceProvider/EventDispatcherProvider.php5
-rw-r--r--app/Subscriber/BaseSubscriber.php1
-rw-r--r--app/Subscriber/LdapUserPhotoSubscriber.php49
-rw-r--r--app/User/LdapUserProvider.php23
-rw-r--r--app/constants.php1
-rw-r--r--config.default.php3
-rw-r--r--doc/index.markdown1
-rw-r--r--doc/ldap-parameters.markdown1
-rw-r--r--doc/ldap-profile-picture.markdown27
-rw-r--r--tests/units/Core/Ldap/LdapUserTest.php70
-rw-r--r--tests/units/Subscriber/LdapUserPhotoSubscriberTest.php83
19 files changed, 377 insertions, 13 deletions
diff --git a/ChangeLog b/ChangeLog
index f9d20cf5..55435654 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -5,6 +5,7 @@ New features:
* Added automated action to change task color based on the priority
* Added support for LDAP Posix Groups (OpenLDAP with memberUid)
+* Added support for LDAP user photo attribute (Avatar image)
* Search in activity stream
* Search in comments
* Search by task creator
diff --git a/app/Auth/LdapAuth.php b/app/Auth/LdapAuth.php
index c9423580..a8dcfcb6 100644
--- a/app/Auth/LdapAuth.php
+++ b/app/Auth/LdapAuth.php
@@ -76,7 +76,7 @@ class LdapAuth extends Base implements PasswordAuthenticationProviderInterface
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
}
- $this->logger->info('Authenticate user: '.$user->getDn());
+ $this->logger->info('Authenticate this user: '.$user->getDn());
if ($client->authenticate($user->getDn(), $this->password)) {
$this->userInfo = $user;
diff --git a/app/Controller/AvatarFile.php b/app/Controller/AvatarFile.php
index 5974cde7..45cb1615 100644
--- a/app/Controller/AvatarFile.php
+++ b/app/Controller/AvatarFile.php
@@ -32,7 +32,7 @@ class AvatarFile extends Base
{
$user = $this->getUser();
- if (! $this->avatarFile->uploadFile($user['id'], $this->request->getFileInfo('avatar'))) {
+ if (! $this->avatarFile->uploadImageFile($user['id'], $this->request->getFileInfo('avatar'))) {
$this->flash->failure(t('Unable to upload the file.'));
}
diff --git a/app/Core/Ldap/User.php b/app/Core/Ldap/User.php
index d5e1eb8a..d0036ea7 100644
--- a/app/Core/Ldap/User.php
+++ b/app/Core/Ldap/User.php
@@ -145,7 +145,8 @@ class User
$entry->getFirstValue($this->getAttributeName()),
$entry->getFirstValue($this->getAttributeEmail()),
$this->getRole($groupIds),
- $groupIds
+ $groupIds,
+ $entry->getFirstValue($this->getAttributePhoto())
);
}
@@ -164,6 +165,7 @@ class User
$this->getAttributeName(),
$this->getAttributeEmail(),
$this->getAttributeGroup(),
+ $this->getAttributePhoto(),
)));
}
@@ -224,6 +226,17 @@ class User
}
/**
+ * Get LDAP profile photo attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getAttributePhoto()
+ {
+ return strtolower(LDAP_USER_ATTRIBUTE_PHOTO);
+ }
+
+ /**
* Get LDAP Group User filter
*
* @access public
diff --git a/app/Core/User/UserProfile.php b/app/Core/User/UserProfile.php
index ef325801..4f873390 100644
--- a/app/Core/User/UserProfile.php
+++ b/app/Core/User/UserProfile.php
@@ -3,6 +3,7 @@
namespace Kanboard\Core\User;
use Kanboard\Core\Base;
+use Kanboard\Event\UserProfileSyncEvent;
/**
* User Profile
@@ -12,6 +13,8 @@ use Kanboard\Core\Base;
*/
class UserProfile extends Base
{
+ const EVENT_USER_PROFILE_AFTER_SYNC = 'user_profile.after.sync';
+
/**
* Assign provider data to the local user
*
@@ -54,6 +57,7 @@ class UserProfile extends Base
if (! empty($profile) && $profile['is_active'] == 1) {
$this->userSession->initialize($profile);
+ $this->dispatcher->dispatch(self::EVENT_USER_PROFILE_AFTER_SYNC, new UserProfileSyncEvent($profile, $user));
return true;
}
diff --git a/app/Core/User/UserSync.php b/app/Core/User/UserSync.php
index d450a0bd..055c7106 100644
--- a/app/Core/User/UserSync.php
+++ b/app/Core/User/UserSync.php
@@ -64,13 +64,13 @@ class UserSync extends Base
*/
private function createUser(UserProviderInterface $user, array $properties)
{
- $id = $this->user->create($properties);
+ $userId = $this->user->create($properties);
- if ($id === false) {
+ if ($userId === false) {
$this->logger->error('Unable to create user profile: '.$user->getExternalId());
return array();
}
- return $this->user->getById($id);
+ return $this->user->getById($userId);
}
}
diff --git a/app/Event/UserProfileSyncEvent.php b/app/Event/UserProfileSyncEvent.php
new file mode 100644
index 00000000..c02e1d89
--- /dev/null
+++ b/app/Event/UserProfileSyncEvent.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Kanboard\Event;
+
+use Kanboard\Core\User\UserProviderInterface;
+use Kanboard\User\LdapUserProvider;
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Class UserProfileSyncEvent
+ *
+ * @package Kanboard\Event
+ * @author Fredic Guillot
+ */
+class UserProfileSyncEvent extends Event
+{
+ /**
+ * User profile
+ *
+ * @var array
+ */
+ private $profile;
+
+ /**
+ * User provider
+ *
+ * @var UserProviderInterface
+ */
+ private $user;
+
+ /**
+ * UserProfileSyncEvent constructor.
+ *
+ * @param array $profile
+ * @param UserProviderInterface $user
+ */
+ public function __construct(array $profile, UserProviderInterface $user)
+ {
+ $this->profile = $profile;
+ $this->user = $user;
+ }
+
+ /**
+ * Get user profile
+ *
+ * @access public
+ * @return array
+ */
+ public function getProfile()
+ {
+ return $this->profile;
+ }
+
+ /**
+ * Get user provider object
+ *
+ * @access public
+ * @return UserProviderInterface|LdapUserProvider
+ */
+ public function getUser()
+ {
+ return $this->user;
+ }
+}
diff --git a/app/Model/AvatarFile.php b/app/Model/AvatarFile.php
index 36784e60..9f47ccc7 100644
--- a/app/Model/AvatarFile.php
+++ b/app/Model/AvatarFile.php
@@ -75,20 +75,20 @@ class AvatarFile extends Base
}
/**
- * Upload avatar image
+ * Upload avatar image file
*
* @access public
* @param integer $user_id
* @param array $file
* @return boolean
*/
- public function uploadFile($user_id, array $file)
+ public function uploadImageFile($user_id, array $file)
{
try {
if ($file['error'] == UPLOAD_ERR_OK && $file['size'] > 0) {
- $destination_filename = $this->generatePath($user_id, $file['name']);
- $this->objectStorage->moveUploadedFile($file['tmp_name'], $destination_filename);
- $this->create($user_id, $destination_filename);
+ $destinationFilename = $this->generatePath($user_id, $file['name']);
+ $this->objectStorage->moveUploadedFile($file['tmp_name'], $destinationFilename);
+ $this->create($user_id, $destinationFilename);
} else {
throw new Exception('File not uploaded: '.var_export($file['error'], true));
}
@@ -102,6 +102,28 @@ class AvatarFile extends Base
}
/**
+ * Upload avatar image content
+ *
+ * @access public
+ * @param integer $user_id
+ * @param string $blob
+ * @return boolean
+ */
+ public function uploadImageContent($user_id, &$blob)
+ {
+ try {
+ $destinationFilename = $this->generatePath($user_id, 'imageContent');
+ $this->objectStorage->put($destinationFilename, $blob);
+ $this->create($user_id, $destinationFilename);
+ } catch (Exception $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* Generate the path for a new filename
*
* @access public
diff --git a/app/ServiceProvider/EventDispatcherProvider.php b/app/ServiceProvider/EventDispatcherProvider.php
index 880caa41..6b3dc098 100644
--- a/app/ServiceProvider/EventDispatcherProvider.php
+++ b/app/ServiceProvider/EventDispatcherProvider.php
@@ -2,6 +2,7 @@
namespace Kanboard\ServiceProvider;
+use Kanboard\Subscriber\LdapUserPhotoSubscriber;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
@@ -28,6 +29,10 @@ class EventDispatcherProvider implements ServiceProviderInterface
$container['dispatcher']->addSubscriber(new TransitionSubscriber($container));
$container['dispatcher']->addSubscriber(new RecurringTaskSubscriber($container));
+ if (LDAP_AUTH && LDAP_USER_ATTRIBUTE_PHOTO !== '') {
+ $container['dispatcher']->addSubscriber(new LdapUserPhotoSubscriber($container));
+ }
+
return $container;
}
}
diff --git a/app/Subscriber/BaseSubscriber.php b/app/Subscriber/BaseSubscriber.php
index 2e41da76..fdea29f6 100644
--- a/app/Subscriber/BaseSubscriber.php
+++ b/app/Subscriber/BaseSubscriber.php
@@ -34,7 +34,6 @@ class BaseSubscriber extends Base
}
$this->called[$key] = true;
-
return false;
}
}
diff --git a/app/Subscriber/LdapUserPhotoSubscriber.php b/app/Subscriber/LdapUserPhotoSubscriber.php
new file mode 100644
index 00000000..3cf46077
--- /dev/null
+++ b/app/Subscriber/LdapUserPhotoSubscriber.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Kanboard\Subscriber;
+
+use Kanboard\Core\User\UserProfile;
+use Kanboard\Event\UserProfileSyncEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Class LdapUserPhotoSubscriber
+ *
+ * @package Kanboard\Subscriber
+ * @author Frederic Guillot
+ */
+class LdapUserPhotoSubscriber extends BaseSubscriber implements EventSubscriberInterface
+{
+ /**
+ * Get event listeners
+ *
+ * @static
+ * @access public
+ * @return array
+ */
+ public static function getSubscribedEvents()
+ {
+ return array(
+ UserProfile::EVENT_USER_PROFILE_AFTER_SYNC => 'syncUserPhoto',
+ );
+ }
+
+ /**
+ * Save the user profile photo from LDAP to the object storage
+ *
+ * @access public
+ * @param UserProfileSyncEvent $event
+ */
+ public function syncUserPhoto(UserProfileSyncEvent $event)
+ {
+ if (is_a($event->getUser(), 'Kanboard\User\LdapUserProvider')) {
+ $profile = $event->getProfile();
+ $photo = $event->getUser()->getPhoto();
+
+ if (empty($profile['avatar_path']) && ! empty($photo)) {
+ $this->logger->info('Saving user photo from LDAP profile');
+ $this->avatarFile->uploadImageContent($profile['id'], $photo);
+ }
+ }
+ }
+}
diff --git a/app/User/LdapUserProvider.php b/app/User/LdapUserProvider.php
index 8d5d4b1f..3e2dcd2b 100644
--- a/app/User/LdapUserProvider.php
+++ b/app/User/LdapUserProvider.php
@@ -61,6 +61,14 @@ class LdapUserProvider implements UserProviderInterface
protected $groupIds;
/**
+ * User photo
+ *
+ * @access protected
+ * @var string
+ */
+ protected $photo = '';
+
+ /**
* Constructor
*
* @access public
@@ -70,8 +78,9 @@ class LdapUserProvider implements UserProviderInterface
* @param string $email
* @param string $role
* @param string[] $groupIds
+ * @param string $photo
*/
- public function __construct($dn, $username, $name, $email, $role, array $groupIds)
+ public function __construct($dn, $username, $name, $email, $role, array $groupIds, $photo = '')
{
$this->dn = $dn;
$this->username = $username;
@@ -79,6 +88,7 @@ class LdapUserProvider implements UserProviderInterface
$this->email = $email;
$this->role = $role;
$this->groupIds = $groupIds;
+ $this->photo = $photo;
}
/**
@@ -203,4 +213,15 @@ class LdapUserProvider implements UserProviderInterface
{
return $this->dn;
}
+
+ /**
+ * Get user photo
+ *
+ * @access public
+ * @return string
+ */
+ public function getPhoto()
+ {
+ return $this->photo;
+ }
}
diff --git a/app/constants.php b/app/constants.php
index 94086fa9..b5b01960 100644
--- a/app/constants.php
+++ b/app/constants.php
@@ -59,6 +59,7 @@ defined('LDAP_USER_ATTRIBUTE_USERNAME') or define('LDAP_USER_ATTRIBUTE_USERNAME'
defined('LDAP_USER_ATTRIBUTE_FULLNAME') or define('LDAP_USER_ATTRIBUTE_FULLNAME', 'cn');
defined('LDAP_USER_ATTRIBUTE_EMAIL') or define('LDAP_USER_ATTRIBUTE_EMAIL', 'mail');
defined('LDAP_USER_ATTRIBUTE_GROUPS') or define('LDAP_USER_ATTRIBUTE_GROUPS', 'memberof');
+defined('LDAP_USER_ATTRIBUTE_PHOTO') or define('LDAP_USER_ATTRIBUTE_PHOTO', '');
defined('LDAP_USER_CREATION') or define('LDAP_USER_CREATION', true);
defined('LDAP_GROUP_ADMIN_DN') or define('LDAP_GROUP_ADMIN_DN', '');
diff --git a/config.default.php b/config.default.php
index 49988a3b..8e5c6e49 100644
--- a/config.default.php
+++ b/config.default.php
@@ -114,6 +114,9 @@ define('LDAP_USER_ATTRIBUTE_EMAIL', 'mail');
// LDAP attribute to find groups in user profile
define('LDAP_USER_ATTRIBUTE_GROUPS', 'memberof');
+// LDAP attribute for user avatar image: thumbnailPhoto or jpegPhoto
+define('LDAP_USER_ATTRIBUTE_PHOTO', '');
+
// Allow automatic LDAP user creation
define('LDAP_USER_CREATION', true);
diff --git a/doc/index.markdown b/doc/index.markdown
index 30083fa9..1294de78 100644
--- a/doc/index.markdown
+++ b/doc/index.markdown
@@ -121,6 +121,7 @@ Technical details
- [LDAP authentication](ldap-authentication.markdown)
- [LDAP group synchronization](ldap-group-sync.markdown)
+- [LDAP profile picture](ldap-profile-picture.markdown)
- [LDAP parameters](ldap-parameters.markdown)
- [Reverse proxy authentication](reverse-proxy-authentication.markdown)
diff --git a/doc/ldap-parameters.markdown b/doc/ldap-parameters.markdown
index c7202641..c18c2b06 100644
--- a/doc/ldap-parameters.markdown
+++ b/doc/ldap-parameters.markdown
@@ -20,6 +20,7 @@ Here is the list of available LDAP parameters:
| `LDAP_USER_ATTRIBUTE_FULLNAME` | cn | LDAP attribute for user full name (Example: "displayname") |
| `LDAP_USER_ATTRIBUTE_EMAIL` | mail | LDAP attribute for user email |
| `LDAP_USER_ATTRIBUTE_GROUPS` | memberof | LDAP attribute to find groups in user profile |
+| `LDAP_USER_ATTRIBUTE_PHOTO` | Empty | LDAP attribute to find user photo (jpegPhoto or thumbnailPhoto |
| `LDAP_USER_CREATION` | true | Enable automatic LDAP user creation |
| `LDAP_GROUP_ADMIN_DN` | Empty | LDAP DN for administrators (Example: "CN=Kanboard-Admins,CN=Users,DC=kanboard,DC=local") |
| `LDAP_GROUP_MANAGER_DN` | Empty | LDAP DN for managers (Example: "CN=Kanboard Managers,CN=Users,DC=kanboard,DC=local") |
diff --git a/doc/ldap-profile-picture.markdown b/doc/ldap-profile-picture.markdown
new file mode 100644
index 00000000..4798f645
--- /dev/null
+++ b/doc/ldap-profile-picture.markdown
@@ -0,0 +1,27 @@
+LDAP User Profile Photo
+=======================
+
+Kanboard can download automatically user pictures from the LDAP server.
+
+This feature is enabled only if LDAP authentication is activated and the parameter `LDAP_USER_ATTRIBUTE_PHOTO` is defined.
+
+Configuration
+-------------
+
+In your `config.php`, you have to set the LDAP attribute used to store the image.
+
+```php
+define('LDAP_USER_ATTRIBUTE_PHOTO', 'jpegPhoto');
+```
+
+Usually, the attributes `jpegPhoto` or `thumbnailPhoto` are used.
+The image can be stored in JPEG or PNG format.
+
+To upload the image in the user profile, Active Directory administrators may use software like [AD Photo Edit](http://www.cjwdev.co.uk/Software/ADPhotoEdit/Info.html).
+
+Notes
+-----
+
+The profile image is **downloaded at login time only if the user do not already have uploaded an image previously**.
+
+To change the user photo, the previous one have to be removed manually in the user profile.
diff --git a/tests/units/Core/Ldap/LdapUserTest.php b/tests/units/Core/Ldap/LdapUserTest.php
index d861871e..02b9331e 100644
--- a/tests/units/Core/Ldap/LdapUserTest.php
+++ b/tests/units/Core/Ldap/LdapUserTest.php
@@ -52,6 +52,7 @@ class LdapUserTest extends Base
'getAttributeEmail',
'getAttributeName',
'getAttributeGroup',
+ 'getAttributePhoto',
'getGroupUserFilter',
'getGroupAdminDn',
'getGroupManagerDn',
@@ -136,10 +137,79 @@ class LdapUserTest extends Base
$this->assertEquals('My LDAP user', $user->getName());
$this->assertEquals('user1@localhost', $user->getEmail());
$this->assertEquals(Role::APP_USER, $user->getRole());
+ $this->assertSame('', $user->getPhoto());
$this->assertEquals(array(), $user->getExternalGroupIds());
$this->assertEquals(array('is_ldap_user' => 1), $user->getExtraAttributes());
}
+ public function testGetUserWithPhoto()
+ {
+ $entries = new Entries(array(
+ 'count' => 1,
+ 0 => array(
+ 'count' => 2,
+ 'dn' => 'uid=my_ldap_user,ou=People,dc=kanboard,dc=local',
+ 'displayname' => array(
+ 'count' => 1,
+ 0 => 'My LDAP user',
+ ),
+ 'mail' => array(
+ 'count' => 2,
+ 0 => 'user1@localhost',
+ 1 => 'user2@localhost',
+ ),
+ 'samaccountname' => array(
+ 'count' => 1,
+ 0 => 'my_ldap_user',
+ ),
+ 'jpegPhoto' => array(
+ 'count' => 1,
+ 0 => 'my photo',
+ ),
+ 0 => 'displayname',
+ 1 => 'mail',
+ 2 => 'samaccountname',
+ )
+ ));
+
+ $this->client
+ ->expects($this->any())
+ ->method('getConnection')
+ ->will($this->returnValue('my_ldap_resource'));
+
+ $this->query
+ ->expects($this->once())
+ ->method('execute')
+ ->with(
+ $this->equalTo('ou=People,dc=kanboard,dc=local'),
+ $this->equalTo('(uid=my_ldap_user)')
+ );
+
+ $this->query
+ ->expects($this->once())
+ ->method('hasResult')
+ ->will($this->returnValue(true));
+
+ $this->query
+ ->expects($this->once())
+ ->method('getEntries')
+ ->will($this->returnValue($entries));
+
+ $this->user
+ ->expects($this->any())
+ ->method('getAttributePhoto')
+ ->will($this->returnValue('jpegPhoto'));
+
+ $this->user
+ ->expects($this->any())
+ ->method('getBasDn')
+ ->will($this->returnValue('ou=People,dc=kanboard,dc=local'));
+
+ $user = $this->user->find('(uid=my_ldap_user)');
+ $this->assertInstanceOf('Kanboard\User\LdapUserProvider', $user);
+ $this->assertEquals('my photo', $user->getPhoto());
+ }
+
public function testGetUserWithAdminRole()
{
$entries = new Entries(array(
diff --git a/tests/units/Subscriber/LdapUserPhotoSubscriberTest.php b/tests/units/Subscriber/LdapUserPhotoSubscriberTest.php
new file mode 100644
index 00000000..df44d232
--- /dev/null
+++ b/tests/units/Subscriber/LdapUserPhotoSubscriberTest.php
@@ -0,0 +1,83 @@
+<?php
+
+use Kanboard\Core\Security\Role;
+use Kanboard\Event\UserProfileSyncEvent;
+use Kanboard\Model\User;
+use Kanboard\Subscriber\LdapUserPhotoSubscriber;
+use Kanboard\User\DatabaseUserProvider;
+use Kanboard\User\LdapUserProvider;
+
+require_once __DIR__.'/../Base.php';
+
+class LdapUserPhotoSubscriberTest extends Base
+{
+ public function testWhenTheProviderIsNotLdap()
+ {
+ $userProvider = new DatabaseUserProvider(array());
+ $subscriber = new LdapUserPhotoSubscriber($this->container);
+ $userModel = new User($this->container);
+
+ $userModel->update(array('id' => 1, 'avatar_path' => 'my avatar'));
+ $user = $userModel->getById(1);
+
+ $subscriber->syncUserPhoto(new UserProfileSyncEvent($user, $userProvider));
+
+ $user = $userModel->getById(1);
+ $this->assertEquals('my avatar', $user['avatar_path']);
+ }
+
+ public function testWhenTheUserHaveLdapPhoto()
+ {
+ $userProvider = new LdapUserProvider('dn', 'admin', 'Admin', 'admin@localhost', Role::APP_ADMIN, array(), 'my photo');
+ $subscriber = new LdapUserPhotoSubscriber($this->container);
+ $userModel = new User($this->container);
+ $user = $userModel->getById(1);
+
+ $this->container['objectStorage']
+ ->expects($this->once())
+ ->method('put')
+ ->with($this->anything(), 'my photo');
+
+
+ $subscriber->syncUserPhoto(new UserProfileSyncEvent($user, $userProvider));
+
+ $user = $userModel->getById(1);
+ $this->assertStringStartsWith('avatars', $user['avatar_path']);
+ }
+
+ public function testWhenTheUserDoNotHaveLdapPhoto()
+ {
+ $userProvider = new LdapUserProvider('dn', 'admin', 'Admin', 'admin@localhost', Role::APP_ADMIN, array());
+ $subscriber = new LdapUserPhotoSubscriber($this->container);
+ $userModel = new User($this->container);
+ $user = $userModel->getById(1);
+
+ $this->container['objectStorage']
+ ->expects($this->never())
+ ->method('put');
+
+ $subscriber->syncUserPhoto(new UserProfileSyncEvent($user, $userProvider));
+
+ $user = $userModel->getById(1);
+ $this->assertEmpty($user['avatar_path']);
+ }
+
+ public function testWhenTheUserAlreadyHaveAvatar()
+ {
+ $userProvider = new LdapUserProvider('dn', 'admin', 'Admin', 'admin@localhost', Role::APP_ADMIN, array(), 'my photo');
+ $subscriber = new LdapUserPhotoSubscriber($this->container);
+ $userModel = new User($this->container);
+
+ $userModel->update(array('id' => 1, 'avatar_path' => 'my avatar'));
+ $user = $userModel->getById(1);
+
+ $this->container['objectStorage']
+ ->expects($this->never())
+ ->method('put');
+
+ $subscriber->syncUserPhoto(new UserProfileSyncEvent($user, $userProvider));
+
+ $user = $userModel->getById(1);
+ $this->assertEquals('my avatar', $user['avatar_path']);
+ }
+}