diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-12-05 20:31:27 -0500 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-12-05 20:31:27 -0500 |
commit | e9fedf3e5cd63aea4da7a71f6647ee427c62fa49 (patch) | |
tree | abc2de5aebace4a2d7c94805552264dab6b10bc7 /app/Core/User | |
parent | 346b8312e5ac877ce3192c2db3a26b500018bbb5 (diff) |
Rewrite of the authentication and authorization system
Diffstat (limited to 'app/Core/User')
-rw-r--r-- | app/Core/User/GroupSync.php | 32 | ||||
-rw-r--r-- | app/Core/User/UserProfile.php | 62 | ||||
-rw-r--r-- | app/Core/User/UserProperty.php | 70 | ||||
-rw-r--r-- | app/Core/User/UserProviderInterface.php | 103 | ||||
-rw-r--r-- | app/Core/User/UserSession.php | 204 | ||||
-rw-r--r-- | app/Core/User/UserSync.php | 76 |
6 files changed, 547 insertions, 0 deletions
diff --git a/app/Core/User/GroupSync.php b/app/Core/User/GroupSync.php new file mode 100644 index 00000000..573acd47 --- /dev/null +++ b/app/Core/User/GroupSync.php @@ -0,0 +1,32 @@ +<?php + +namespace Kanboard\Core\User; + +use Kanboard\Core\Base; + +/** + * Group Synchronization + * + * @package user + * @author Frederic Guillot + */ +class GroupSync extends Base +{ + /** + * Synchronize group membership + * + * @access public + * @param integer $userId + * @param array $groupIds + */ + public function synchronize($userId, array $groupIds) + { + foreach ($groupIds as $groupId) { + $group = $this->group->getByExternalId($groupId); + + if (! empty($group) && ! $this->groupMember->isMember($group['id'], $userId)) { + $this->groupMember->addUser($group['id'], $userId); + } + } + } +} diff --git a/app/Core/User/UserProfile.php b/app/Core/User/UserProfile.php new file mode 100644 index 00000000..ccbc7f06 --- /dev/null +++ b/app/Core/User/UserProfile.php @@ -0,0 +1,62 @@ +<?php + +namespace Kanboard\Core\User; + +use Kanboard\Core\Base; + +/** + * User Profile + * + * @package user + * @author Frederic Guillot + */ +class UserProfile extends Base +{ + /** + * Assign provider data to the local user + * + * @access public + * @param integer $userId + * @param UserProviderInterface $user + * @return boolean + */ + public function assign($userId, UserProviderInterface $user) + { + $profile = $this->user->getById($userId); + + $values = UserProperty::filterProperties($profile, UserProperty::getProperties($user)); + $values['id'] = $userId; + + if ($this->user->update($values)) { + $profile = array_merge($profile, $values); + $this->userSession->initialize($profile); + return true; + } + + return false; + } + + /** + * Synchronize user properties with the local database and create the user session + * + * @access public + * @param UserProviderInterface $user + * @return boolean + */ + public function initialize(UserProviderInterface $user) + { + if ($user->getInternalId()) { + $profile = $this->user->getById($user->getInternalId()); + } elseif ($user->getExternalIdColumn() && $user->getExternalId()) { + $profile = $this->userSync->synchronize($user); + $this->groupSync->synchronize($profile['id'], $user->getExternalGroupIds()); + } + + if (! empty($profile)) { + $this->userSession->initialize($profile); + return true; + } + + return false; + } +} diff --git a/app/Core/User/UserProperty.php b/app/Core/User/UserProperty.php new file mode 100644 index 00000000..f8b08a3d --- /dev/null +++ b/app/Core/User/UserProperty.php @@ -0,0 +1,70 @@ +<?php + +namespace Kanboard\Core\User; + +/** + * User Property + * + * @package user + * @author Frederic Guillot + */ +class UserProperty +{ + /** + * Get filtered user properties from user provider + * + * @static + * @access public + * @param UserProviderInterface $user + * @return array + */ + public static function getProperties(UserProviderInterface $user) + { + $properties = array( + 'username' => $user->getUsername(), + 'name' => $user->getName(), + 'email' => $user->getEmail(), + 'role' => $user->getRole(), + $user->getExternalIdColumn() => $user->getExternalId(), + ); + + $properties = array_merge($properties, $user->getExtraAttributes()); + + return array_filter($properties, array(__NAMESPACE__.'\UserProperty', 'isNotEmptyValue')); + } + + /** + * Filter user properties compared to existing user profile + * + * @static + * @access public + * @param array $profile + * @param array $properties + * @return array + */ + public static function filterProperties(array $profile, array $properties) + { + $values = array(); + + foreach ($properties as $property => $value) { + if (array_key_exists($property, $profile) && ! self::isNotEmptyValue($profile[$property])) { + $values[$property] = $value; + } + } + + return $values; + } + + /** + * Check if a value is not empty + * + * @static + * @access public + * @param string $value + * @return boolean + */ + public static function isNotEmptyValue($value) + { + return $value !== null && $value !== ''; + } +} diff --git a/app/Core/User/UserProviderInterface.php b/app/Core/User/UserProviderInterface.php new file mode 100644 index 00000000..07e01f42 --- /dev/null +++ b/app/Core/User/UserProviderInterface.php @@ -0,0 +1,103 @@ +<?php + +namespace Kanboard\Core\User; + +/** + * User Provider Interface + * + * @package user + * @author Frederic Guillot + */ +interface UserProviderInterface +{ + /** + * Return true to allow automatic user creation + * + * @access public + * @return boolean + */ + public function isUserCreationAllowed(); + + /** + * Get external id column name + * + * Example: google_id, github_id, gitlab_id... + * + * @access public + * @return string + */ + public function getExternalIdColumn(); + + /** + * Get internal id + * + * If a value is returned the user properties won't be updated in the local database + * + * @access public + * @return integer + */ + public function getInternalId(); + + /** + * Get external id + * + * @access public + * @return string + */ + public function getExternalId(); + + /** + * Get user role + * + * Return an empty string to not override role stored in the database + * + * @access public + * @return string + */ + public function getRole(); + + /** + * Get username + * + * @access public + * @return string + */ + public function getUsername(); + + /** + * Get user full name + * + * @access public + * @return string + */ + public function getName(); + + /** + * Get user email + * + * @access public + * @return string + */ + public function getEmail(); + + /** + * Get external group ids + * + * A synchronization is done at login time, + * the user will be member of those groups if they exists in the database + * + * @access public + * @return string[] + */ + public function getExternalGroupIds(); + + /** + * Get extra user attributes + * + * Example: is_ldap_user, disable_login_form, notifications_enabled... + * + * @access public + * @return array + */ + public function getExtraAttributes(); +} diff --git a/app/Core/User/UserSession.php b/app/Core/User/UserSession.php new file mode 100644 index 00000000..d1e0bb93 --- /dev/null +++ b/app/Core/User/UserSession.php @@ -0,0 +1,204 @@ +<?php + +namespace Kanboard\Core\User; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Role; + +/** + * User Session + * + * @package user + * @author Frederic Guillot + */ +class UserSession extends Base +{ + /** + * Update user session + * + * @access public + * @param array $user + */ + public function initialize(array $user) + { + foreach (array('password', 'is_admin', 'is_project_admin', 'twofactor_secret') as $column) { + if (isset($user[$column])) { + unset($user[$column]); + } + } + + $user['id'] = (int) $user['id']; + $user['is_ldap_user'] = isset($user['is_ldap_user']) ? (bool) $user['is_ldap_user'] : false; + $user['twofactor_activated'] = isset($user['twofactor_activated']) ? (bool) $user['twofactor_activated'] : false; + + $this->sessionStorage->user = $user; + $this->sessionStorage->postAuthenticationValidated = false; + } + + /** + * Get user application role + * + * @access public + * @return string + */ + public function getRole() + { + return $this->sessionStorage->user['role']; + } + + /** + * Return true if the user has validated the 2FA key + * + * @access public + * @return bool + */ + public function isPostAuthenticationValidated() + { + return isset($this->sessionStorage->postAuthenticationValidated) && $this->sessionStorage->postAuthenticationValidated === true; + } + + /** + * Validate 2FA for the current session + * + * @access public + */ + public function validatePostAuthentication() + { + $this->sessionStorage->postAuthenticationValidated = true; + } + + /** + * Return true if the user has 2FA enabled + * + * @access public + * @return bool + */ + public function hasPostAuthentication() + { + return isset($this->sessionStorage->user['twofactor_activated']) && $this->sessionStorage->user['twofactor_activated'] === true; + } + + /** + * Disable 2FA for the current session + * + * @access public + */ + public function disablePostAuthentication() + { + $this->sessionStorage->user['twofactor_activated'] = false; + } + + /** + * Return true if the logged user is admin + * + * @access public + * @return bool + */ + public function isAdmin() + { + return isset($this->sessionStorage->user['role']) && $this->sessionStorage->user['role'] === Role::APP_ADMIN; + } + + /** + * Get the connected user id + * + * @access public + * @return integer + */ + public function getId() + { + return isset($this->sessionStorage->user['id']) ? (int) $this->sessionStorage->user['id'] : 0; + } + + /** + * Get username + * + * @access public + * @return integer + */ + public function getUsername() + { + return isset($this->sessionStorage->user['username']) ? $this->sessionStorage->user['username'] : ''; + } + + /** + * Check is the user is connected + * + * @access public + * @return bool + */ + public function isLogged() + { + return isset($this->sessionStorage->user) && ! empty($this->sessionStorage->user); + } + + /** + * Get project filters from the session + * + * @access public + * @param integer $project_id + * @return string + */ + public function getFilters($project_id) + { + return ! empty($this->sessionStorage->filters[$project_id]) ? $this->sessionStorage->filters[$project_id] : 'status:open'; + } + + /** + * Save project filters in the session + * + * @access public + * @param integer $project_id + * @param string $filters + */ + public function setFilters($project_id, $filters) + { + $this->sessionStorage->filters[$project_id] = $filters; + } + + /** + * Is board collapsed or expanded + * + * @access public + * @param integer $project_id + * @return boolean + */ + public function isBoardCollapsed($project_id) + { + return ! empty($this->sessionStorage->boardCollapsed[$project_id]) ? $this->sessionStorage->boardCollapsed[$project_id] : false; + } + + /** + * Set board display mode + * + * @access public + * @param integer $project_id + * @param boolean $is_collapsed + */ + public function setBoardDisplayMode($project_id, $is_collapsed) + { + $this->sessionStorage->boardCollapsed[$project_id] = $is_collapsed; + } + + /** + * Set comments sorting + * + * @access public + * @param string $order + */ + public function setCommentSorting($order) + { + $this->sessionStorage->commentSorting = $order; + } + + /** + * Get comments sorting direction + * + * @access public + * @return string + */ + public function getCommentSorting() + { + return empty($this->sessionStorage->commentSorting) ? 'ASC' : $this->sessionStorage->commentSorting; + } +} diff --git a/app/Core/User/UserSync.php b/app/Core/User/UserSync.php new file mode 100644 index 00000000..d450a0bd --- /dev/null +++ b/app/Core/User/UserSync.php @@ -0,0 +1,76 @@ +<?php + +namespace Kanboard\Core\User; + +use Kanboard\Core\Base; + +/** + * User Synchronization + * + * @package user + * @author Frederic Guillot + */ +class UserSync extends Base +{ + /** + * Synchronize user profile + * + * @access public + * @param UserProviderInterface $user + * @return array + */ + public function synchronize(UserProviderInterface $user) + { + $profile = $this->user->getByExternalId($user->getExternalIdColumn(), $user->getExternalId()); + $properties = UserProperty::getProperties($user); + + if (! empty($profile)) { + $profile = $this->updateUser($profile, $properties); + } elseif ($user->isUserCreationAllowed()) { + $profile = $this->createUser($user, $properties); + } + + return $profile; + } + + /** + * Update user profile + * + * @access public + * @param array $profile + * @param array $properties + * @return array + */ + private function updateUser(array $profile, array $properties) + { + $values = UserProperty::filterProperties($profile, $properties); + + if (! empty($values)) { + $values['id'] = $profile['id']; + $result = $this->user->update($values); + return $result ? array_merge($profile, $properties) : $profile; + } + + return $profile; + } + + /** + * Create user + * + * @access public + * @param UserProviderInterface $user + * @param array $properties + * @return array + */ + private function createUser(UserProviderInterface $user, array $properties) + { + $id = $this->user->create($properties); + + if ($id === false) { + $this->logger->error('Unable to create user profile: '.$user->getExternalId()); + return array(); + } + + return $this->user->getById($id); + } +} |