'lib' rewritten.

This commit is contained in:
Marcin Łojewski
2018-03-02 22:56:13 +01:00
parent ed5ec82479
commit c1cc89f456
62 changed files with 4788 additions and 2971 deletions

136
lib/Action/EmailSync.php Normal file
View File

@@ -0,0 +1,136 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Action;
use OCA\UserSQL\Constant\App;
use OCA\UserSQL\Constant\Opt;
use OCA\UserSQL\Model\User;
use OCA\UserSQL\Properties;
use OCA\UserSQL\Repository\UserRepository;
use OCP\IConfig;
use OCP\ILogger;
/**
* Synchronizes the user email address.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class EmailSync implements IUserAction
{
/**
* @var string The application name.
*/
private $appName;
/**
* @var ILogger The logger instance.
*/
private $logger;
/**
* @var Properties The properties array.
*/
private $properties;
/**
* @var IConfig The config instance.
*/
private $config;
/**
* @var UserRepository The user repository.
*/
private $userRepository;
/**
* The default constructor.
*
* @param string $appName The application name.
* @param ILogger $logger The logger instance.
* @param Properties $properties The properties array.
* @param IConfig $config The config instance.
* @param UserRepository $userRepository The user repository.
*/
public function __construct(
$appName, ILogger $logger, Properties $properties, IConfig $config,
UserRepository $userRepository
) {
$this->appName = $appName;
$this->logger = $logger;
$this->properties = $properties;
$this->config = $config;
$this->userRepository = $userRepository;
}
/**
* @inheritdoc
*/
public function doAction(User $user)
{
$this->logger->debug(
"Entering EmailSync#doAction($user->uid)", ["app" => $this->appName]
);
$ncMail = $this->config->getUserValue(
$user->uid, "settings", "email", ""
);
$result = false;
switch ($this->properties[Opt::EMAIL_SYNC]) {
case App::EMAIL_INITIAL:
if (empty($ncMail) && !empty($user->email)) {
$this->config->setUserValue(
$user->uid, "settings", "email", $user->email
);
}
$result = true;
break;
case App::EMAIL_FORCE_NC:
if (!empty($ncMail) && $user->email !== $ncMail) {
$user = $this->userRepository->findByUid($user->uid);
if (!($user instanceof User)) {
break;
}
$user->email = $ncMail;
$result = $this->userRepository->save($user);
}
break;
case App::EMAIL_FORCE_SQL:
if (!empty($user->email) && $user->email !== $ncMail) {
$this->config->setUserValue(
$user->uid, "settings", "email", $user->email
);
}
$result = true;
break;
}
$this->logger->debug(
"Returning EmailSync#doAction($user->uid): " . ($result ? "true"
: "false"),
["app" => $this->appName]
);
return $result;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Action;
use OCA\UserSQL\Model\User;
/**
* Action to execute every time an user account is queried.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
interface IUserAction
{
/**
* Execute an action.
*
* @param User $user The user entity.
*
* @return bool The action status.
*/
public function doAction(User $user);
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\AppInfo;
use OCP\AppFramework\App;
use OCP\AppFramework\QueryException;
/**
* The application bootstrap class.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Application extends App
{
/**
* The class constructor.
*
* @param array $urlParams An array with variables extracted
* from the routes.
*/
public function __construct(array $urlParams = array())
{
parent::__construct('user_sql', $urlParams);
}
/**
* Register the application backends
* if all necessary configuration is provided.
*
* @throws QueryException If the query container's could not be resolved
*/
public function registerBackends()
{
$userBackend = $this->getContainer()->query(
'\OCA\UserSQL\Backend\UserBackend'
);
$groupBackend = $this->getContainer()->query(
'\OCA\UserSQL\Backend\GroupBackend'
);
if ($userBackend->isConfigured()) {
\OC::$server->getUserManager()->registerBackend($userBackend);
}
if ($groupBackend->isConfigured()) {
\OC::$server->getGroupManager()->addBackend($groupBackend);
}
}
}

View File

@@ -0,0 +1,457 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Backend;
use OC\Group\Backend;
use OCA\UserSQL\Cache;
use OCA\UserSQL\Constant\DB;
use OCA\UserSQL\Model\Group;
use OCA\UserSQL\Properties;
use OCA\UserSQL\Repository\GroupRepository;
use OCP\ILogger;
/**
* The SQL group backend manager.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
final class GroupBackend extends Backend
{
/**
* @var string The application name.
*/
private $appName;
/**
* @var ILogger The logger instance.
*/
private $logger;
/**
* @var Cache The cache instance.
*/
private $cache;
/**
* @var GroupRepository The group repository.
*/
private $groupRepository;
/**
* @var Properties The properties array.
*/
private $properties;
/**
* The default constructor.
*
* @param string $AppName The application name.
* @param Cache $cache The cache instance.
* @param ILogger $logger The logger instance.
* @param Properties $properties The properties array.
* @param GroupRepository $groupRepository The group repository.
*/
public function __construct(
$AppName, Cache $cache, ILogger $logger, Properties $properties,
GroupRepository $groupRepository
) {
$this->appName = $AppName;
$this->cache = $cache;
$this->logger = $logger;
$this->properties = $properties;
$this->groupRepository = $groupRepository;
}
/**
* @inheritdoc
*/
public function getGroups($search = "", $limit = null, $offset = null)
{
$this->logger->debug(
"Entering getGroups($search, $limit, $offset)",
["app" => $this->appName]
);
$cacheKey = self::class . "groups_" . $search . "_" . $limit . "_"
. $offset;
$groups = $this->cache->get($cacheKey);
if (!is_null($groups)) {
$this->logger->debug(
"Returning from cache getGroups($search, $limit, $offset): count("
. count($groups) . ")", ["app" => $this->appName]
);
return $groups;
}
$groups = $this->groupRepository->findAllBySearchTerm(
"%" . $search . "%", $limit, $offset
);
if ($groups === false) {
return [];
}
foreach ($groups as $group) {
$this->cache->set("group_" . $group->gid, $group);
}
$groups = array_map(
function ($group) {
return $group->gid;
}, $groups
);
$this->cache->set($cacheKey, $groups);
$this->logger->debug(
"Returning getGroups($search, $limit, $offset): count(" . count(
$groups
) . ")", ["app" => $this->appName]
);
return $groups;
}
/**
* Returns the number of users in given group matching the search term.
*
* @param string $gid The group ID.
* @param string $search The search term.
*
* @return int The number of users in given group matching the search term.
*/
public function countUsersInGroup($gid, $search = "")
{
$this->logger->debug(
"Entering countUsersInGroup($gid, $search)",
["app" => $this->appName]
);
$cacheKey = self::class . "users#_" . $gid . "_" . $search;
$count = $this->cache->get($cacheKey);
if (!is_null($count)) {
$this->logger->debug(
"Returning from cache countUsersInGroup($gid, $search): $count",
["app" => $this->appName]
);
return $count;
}
$count = $this->groupRepository->countAll($gid, "%" . $search . "%");
if ($count === false) {
return 0;
}
$this->cache->set($cacheKey, $count);
$this->logger->debug(
"Returning countUsersInGroup($gid, $search): $count",
["app" => $this->appName]
);
return $count;
}
/**
* @inheritdoc
*/
public function inGroup($uid, $gid)
{
$this->logger->debug(
"Entering inGroup($uid, $gid)", ["app" => $this->appName]
);
$cacheKey = self::class . "user_group_" . $uid . "_" . $gid;
$inGroup = $this->cache->get($cacheKey);
if (!is_null($inGroup)) {
$this->logger->debug(
"Returning from cache inGroup($uid, $gid): " . ($inGroup
? "true" : "false"), ["app" => $this->appName]
);
return $inGroup;
}
$inGroup = in_array($gid, $this->getUserGroups($uid));
$this->cache->set($cacheKey, $inGroup);
$this->logger->debug(
"Returning inGroup($uid, $gid): " . ($inGroup ? "true" : "false"),
["app" => $this->appName]
);
return $inGroup;
}
/**
* @inheritdoc
*/
public function getUserGroups($uid)
{
$this->logger->debug(
"Entering getUserGroups($uid)", ["app" => $this->appName]
);
$cacheKey = self::class . "user_groups_" . $uid;
$groups = $this->cache->get($cacheKey);
if (!is_null($groups)) {
$this->logger->debug(
"Returning from cache getUserGroups($uid): count(" . count(
$groups
) . ")", ["app" => $this->appName]
);
return $groups;
}
$groups = $this->groupRepository->findAllByUid($uid);
if ($groups === false) {
return [];
}
foreach ($groups as $group) {
$this->cache->set("group_" . $group->gid, $group);
}
$groups = array_map(
function ($group) {
return $group->gid;
}, $groups
);
$this->cache->set($cacheKey, $groups);
$this->logger->debug(
"Returning getUserGroups($uid): count(" . count(
$groups
) . ")", ["app" => $this->appName]
);
return $groups;
}
/**
* @inheritdoc
*/
public function groupExists($gid)
{
$this->logger->debug(
"Entering groupExists($gid)", ["app" => $this->appName]
);
$group = $this->getGroup($gid);
if ($group === false) {
return false;
}
$exists = !is_null($group);
$this->logger->debug(
"Returning groupExists($gid): " . ($exists ? "true" : "false"),
["app" => $this->appName]
);
return $exists;
}
/**
* Get a group entity object. If it's found value from cache is used.
*
* @param $gid $uid The group ID.
*
* @return Group The group entity, NULL if it does not exists or
* FALSE on failure.
*/
private function getGroup($gid)
{
$cacheKey = self::class . "group_" . $gid;
$cachedGroup = $this->cache->get($cacheKey);
if (!is_null($cachedGroup)) {
if ($cachedGroup === false) {
$this->logger->debug(
"Found null group in cache: $gid", ["app" => $this->appName]
);
return null;
}
$group = new Group();
foreach ($cachedGroup as $key => $value) {
$group->{$key} = $value;
}
$this->logger->debug(
"Found group in cache: " . $group->gid,
["app" => $this->appName]
);
return $group;
}
$group = $this->groupRepository->findByGid($gid);
if ($group instanceof Group) {
$this->cache->set($cacheKey, $group);
} elseif (is_null($group)) {
$this->cache->set($cacheKey, false);
}
return $group;
}
/**
* @inheritdoc
*/
public function usersInGroup($gid, $search = "", $limit = -1, $offset = 0)
{
$this->logger->debug(
"Entering usersInGroup($gid, $search, $limit, $offset)",
["app" => $this->appName]
);
$cacheKey = self::class . "group_users_" . $gid . "_" . $search . "_"
. $limit . "_" . $offset;
$users = $this->cache->get($cacheKey);
if (!is_null($users)) {
$this->logger->debug(
"Returning from cache usersInGroup($gid, $search, $limit, $offset): count("
. count($users) . ")", ["app" => $this->appName]
);
return $users;
}
$uids = $this->groupRepository->findAllUidsBySearchTerm(
$gid, "%" . $search . "%", $limit, $offset
);
if ($uids === false) {
return [];
}
$this->cache->set($cacheKey, $uids);
$this->logger->debug(
"Returning usersInGroup($gid, $search, $limit, $offset): count("
. count($uids) . ")", ["app" => $this->appName]
);
return $uids;
}
/**
* Checks if a user is in the admin group.
*
* @param string $uid User ID.
*
* @return bool TRUE if a user is in the admin group, FALSE otherwise.
*/
public function isAdmin($uid)
{
$this->logger->debug(
"Entering isAdmin($uid)", ["app" => $this->appName]
);
$cacheKey = self::class . "admin_" . $uid;
$admin = $this->cache->get($cacheKey);
if (!is_null($admin)) {
$this->logger->debug(
"Returning from cache isAdmin($uid): " . ($admin ? "true"
: "false"), ["app" => $this->appName]
);
return $admin;
}
$admin = $this->groupRepository->belongsToAdmin($uid);
if (is_null($admin)) {
return false;
}
$this->cache->set($cacheKey, $admin);
$this->logger->debug(
"Returning isAdmin($uid): " . ($admin ? "true" : "false"),
["app" => $this->appName]
);
return $admin;
}
/**
* Get associative array of the group details.
*
* @param string $gid The group ID.
*
* @return array Associative array of the group details.
*/
public function getGroupDetails($gid)
{
$this->logger->debug(
"Entering getGroupDetails($gid)", ["app" => $this->appName]
);
$group = $this->getGroup($gid);
if (!($group instanceof Group)) {
return [];
}
$details = ["displayName" => $group->name];
$this->logger->debug(
"Returning getGroupDetails($gid): " . implode(", ", $details),
["app" => $this->appName]
);
return $details;
}
/**
* @inheritdoc
*/
public function getSupportedActions()
{
$actions = parent::getSupportedActions();
$actions &= empty($this->properties[DB::GROUP_ADMIN_COLUMN])
? ~Backend::IS_ADMIN : ~0;
$actions &= empty($this->properties[DB::GROUP_NAME_COLUMN])
? ~Backend::GROUP_DETAILS : ~0;
return $actions;
}
/**
* Check if this backend is correctly set and can be enabled.
*
* @return bool TRUE if all necessary options for this backend
* are configured, FALSE otherwise.
*/
public function isConfigured()
{
return !empty($this->properties[DB::DATABASE])
&& !empty($this->properties[DB::DRIVER])
&& !empty($this->properties[DB::HOSTNAME])
&& !empty($this->properties[DB::USERNAME])
&& !empty($this->properties[DB::GROUP_TABLE])
&& !empty($this->properties[DB::USER_GROUP_TABLE])
&& !empty($this->properties[DB::GROUP_GID_COLUMN])
&& !empty($this->properties[DB::USER_GROUP_GID_COLUMN])
&& !empty($this->properties[DB::USER_GROUP_UID_COLUMN]);
}
}

566
lib/Backend/UserBackend.php Normal file
View File

@@ -0,0 +1,566 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Backend;
use OC\User\Backend;
use OCA\UserSQL\Action\EmailSync;
use OCA\UserSQL\Action\IUserAction;
use OCA\UserSQL\Cache;
use OCA\UserSQL\Constant\App;
use OCA\UserSQL\Constant\DB;
use OCA\UserSQL\Constant\Opt;
use OCA\UserSQL\Crypto\IPasswordAlgorithm;
use OCA\UserSQL\Model\User;
use OCA\UserSQL\Properties;
use OCA\UserSQL\Repository\UserRepository;
use OCP\IConfig;
use OCP\IL10N;
use OCP\ILogger;
/**
* The SQL user backend manager.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
final class UserBackend extends Backend
{
/**
* @var string The application name.
*/
private $appName;
/**
* @var ILogger The logger instance.
*/
private $logger;
/**
* @var Cache The cache instance.
*/
private $cache;
/**
* @var UserRepository The user repository.
*/
private $userRepository;
/**
* @var Properties The properties array.
*/
private $properties;
/**
* @var IL10N The localization service.
*/
private $localization;
/**
* @var IConfig The config instance.
*/
private $config;
/**
* @var IUserAction[] The actions to execute.
*/
private $actions;
/**
* The default constructor.
*
* @param string $AppName The application name.
* @param Cache $cache The cache instance.
* @param ILogger $logger The logger instance.
* @param Properties $properties The properties array.
* @param UserRepository $userRepository The user repository.
* @param IL10N $localization The localization service.
* @param IConfig $config The config instance.
*/
public function __construct(
$AppName, Cache $cache, ILogger $logger, Properties $properties,
UserRepository $userRepository, IL10N $localization, IConfig $config
) {
$this->appName = $AppName;
$this->cache = $cache;
$this->logger = $logger;
$this->properties = $properties;
$this->userRepository = $userRepository;
$this->localization = $localization;
$this->config = $config;
$this->actions = [];
$this->initActions();
}
/**
* Initiate the actions array.
*/
private function initActions()
{
if (!empty($this->properties[Opt::EMAIL_SYNC])
&& !empty($this->properties[DB::USER_EMAIL_COLUMN])
) {
$this->actions[] = new EmailSync(
$this->appName, $this->logger, $this->properties, $this->config,
$this->userRepository
);
}
}
/**
* @inheritdoc
*/
public function hasUserListings()
{
return true;
}
/**
* Count users in the database.
*
* @return int The number of users.
*/
public function countUsers()
{
$this->logger->debug(
"Entering countUsers()", ["app" => $this->appName]
);
$cacheKey = self::class . "users#";
$count = $this->cache->get($cacheKey);
if (!is_null($count)) {
$this->logger->debug(
"Returning from cache countUsers(): $count",
["app" => $this->appName]
);
return $count;
}
$count = $this->userRepository->countAll("%");
if ($count === false) {
return 0;
}
$this->cache->set($cacheKey, $count);
$this->logger->debug(
"Returning countUsers(): $count", ["app" => $this->appName]
);
return $count;
}
/**
* @inheritdoc
*/
public function userExists($uid)
{
$this->logger->debug(
"Entering userExists($uid)", ["app" => $this->appName]
);
$user = $this->getUser($uid);
if ($user === false) {
return false;
}
$exists = !is_null($user);
$this->logger->debug(
"Returning userExists($uid): " . ($exists ? "true" : "false"),
["app" => $this->appName]
);
return $exists;
}
/**
* Get a user entity object. If it's found value from cache is used.
*
* @param string $uid The user ID.
*
* @return User The user entity, NULL if it does not exists or
* FALSE on failure.
*/
private function getUser($uid)
{
$cacheKey = self::class . "user_" . $uid;
$cachedUser = $this->cache->get($cacheKey);
if (!is_null($cachedUser)) {
$user = new User();
foreach ($cachedUser as $key => $value) {
$user->{$key} = $value;
}
$this->logger->debug(
"Found user in cache: " . $user->uid, ["app" => $this->appName]
);
return $user;
}
$user = $this->userRepository->findByUid($uid);
if ($user instanceof User) {
$this->cache->set($cacheKey, $user);
foreach ($this->actions as $action) {
$action->doAction($user);
}
}
return $user;
}
/**
* @inheritdoc
*/
public function getDisplayName($uid)
{
$this->logger->debug(
"Entering getDisplayName($uid)", ["app" => $this->appName]
);
$user = $this->getUser($uid);
if (!($user instanceof User)) {
return false;
}
$name = $user->name;
$this->logger->debug(
"Returning getDisplayName($uid): $name",
["app" => $this->appName]
);
return $name;
}
/**
* Check if the user's password is correct then return its ID or
* FALSE on failure.
*
* @param string $uid The user ID.
* @param string $password The password.
*
* @return string|bool The user ID on success, false otherwise.
*/
public function checkPassword($uid, $password)
{
$this->logger->debug(
"Entering checkPassword($uid, *)", ["app" => $this->appName]
);
$passwordAlgorithm = $this->getPasswordAlgorithm();
if ($passwordAlgorithm === null) {
return false;
}
$user = $this->userRepository->findByUid($uid);
if (!($user instanceof User)) {
return false;
}
$isCorrect = $passwordAlgorithm->checkPassword(
$password, $user->password
);
if ($isCorrect !== true) {
$this->logger->info(
"Invalid password attempt for user: $uid",
["app" => $this->appName]
);
return false;
}
$this->logger->info(
"Successful password attempt for user: $uid",
["app" => $this->appName]
);
return $uid;
}
/**
* Get a password algorithm implementation instance.
*
* @return IPasswordAlgorithm The password algorithm instance or FALSE
* on failure.
*/
private function getPasswordAlgorithm()
{
$cryptoType = $this->properties[Opt::CRYPTO_CLASS];
$passwordAlgorithm = new $cryptoType($this->localization);
if ($passwordAlgorithm === null) {
$this->logger->error(
"Cannot get password algorithm instance: " . $cryptoType,
["app" => $this->appName]
);
}
return $passwordAlgorithm;
}
/**
* @inheritdoc
*/
public function getDisplayNames($search = "", $limit = null, $offset = null)
{
$this->logger->debug(
"Entering getDisplayNames($search, $limit, $offset)",
["app" => $this->appName]
);
$users = $this->getUsers($search, $limit, $offset);
$names = [];
foreach ($users as $user) {
$names[$user->uid] = $user->name;
}
$this->logger->debug(
"Returning getDisplayNames($search, $limit, $offset): count("
. count($users) . ")", ["app" => $this->appName]
);
return $names;
}
/**
* @inheritdoc
*/
public function getUsers($search = "", $limit = null, $offset = null)
{
$this->logger->debug(
"Entering getUsers($search, $limit, $offset)",
["app" => $this->appName]
);
$cacheKey = self::class . "users_" . $search . "_" . $limit . "_"
. $offset;
$users = $this->cache->get($cacheKey);
if (!is_null($users)) {
$this->logger->debug(
"Returning from cache getUsers($search, $limit, $offset): count("
. count($users) . ")", ["app" => $this->appName]
);
return $users;
}
$users = $this->userRepository->findAllBySearchTerm(
"%" . $search . "%", $limit, $offset
);
if ($users === false) {
return [];
}
foreach ($users as $user) {
$this->cache->set("user_" . $user->uid, $user);
}
$users = array_map(
function ($user) {
return $user->uid;
}, $users
);
$this->cache->set($cacheKey, $users);
$this->logger->debug(
"Returning getUsers($search, $limit, $offset): count(" . count(
$users
) . ")", ["app" => $this->appName]
);
return $users;
}
/**
* Set a user password.
*
* @param string $uid The user ID.
* @param string $password The password to set.
*
* @return bool TRUE if the password has been set, FALSE otherwise.
*/
public function setPassword($uid, $password)
{
$this->logger->debug(
"Entering setPassword($uid, *)", ["app" => "user_sql"]
);
$passwordAlgorithm = $this->getPasswordAlgorithm();
if ($passwordAlgorithm === false) {
return false;
}
$passwordHash = $passwordAlgorithm->getPasswordHash($password);
if ($passwordHash === false) {
return false;
}
$user = $this->userRepository->findByUid($uid);
if (!($user instanceof User)) {
return false;
}
$user->password = $passwordHash;
$result = $this->userRepository->save($user);
if ($result === true) {
$this->logger->info(
"Password has been set successfully for user: $uid",
["app" => $this->appName]
);
return true;
}
return false;
}
/**
* @inheritdoc
*/
public function getHome($uid)
{
$this->logger->debug(
"Entering getHome($uid)", ["app" => $this->appName]
);
$home = false;
switch ($this->properties[Opt::HOME_MODE]) {
case App::HOME_STATIC:
$home = $this->properties[Opt::HOME_LOCATION];
$home = str_replace("%u", $uid, $home);
break;
case App::HOME_QUERY:
$user = $this->getUser($uid);
if (!($user instanceof User)) {
return false;
}
$home = $user->home;
break;
}
$this->logger->debug(
"Returning getHome($uid): " . $home, ["app" => $this->appName]
);
return $home;
}
/**
* Can user change its avatar.
*
* @param string $uid The user ID.
*
* @return bool TRUE if the user can change its avatar, FALSE otherwise.
*/
public function canChangeAvatar($uid)
{
$this->logger->debug(
"Entering canChangeAvatar($uid)", ["app" => $this->appName]
);
$user = $this->userRepository->findByUid($uid);
if (!($user instanceof User)) {
return false;
}
$avatar = $user->avatar;
$this->logger->debug(
"Returning canChangeAvatar($uid): " . ($avatar ? "true"
: "false"), ["app" => $this->appName]
);
return $avatar;
}
/**
* Set a user display name.
*
* @param string $uid The user ID.
* @param string $displayName The display name to set.
*
* @return bool TRUE if the password has been set, FALSE otherwise.
*/
public function setDisplayName($uid, $displayName)
{
$this->logger->debug(
"Entering setDisplayName($uid, $displayName)",
["app" => $this->appName]
);
$user = $this->userRepository->findByUid($uid);
if (!($user instanceof User)) {
return false;
}
$user->name = $displayName;
$result = $this->userRepository->save($user);
if ($result === true) {
$this->logger->info(
"Display name has been set successfully for user: $uid",
["app" => $this->appName]
);
return true;
}
return false;
}
/**
* @inheritdoc
*/
public function getSupportedActions()
{
$actions = parent::getSupportedActions();
$actions &= empty($this->properties[DB::USER_NAME_COLUMN])
? ~Backend::GET_DISPLAYNAME : ~0;
$actions &= empty($this->properties[Opt::HOME_MODE])
? ~Backend::GET_HOME : ~0;
$actions &= empty($this->properties[DB::USER_AVATAR_COLUMN])
? ~Backend::PROVIDE_AVATAR : ~0;
$actions &= (!empty($this->properties[DB::USER_NAME_COLUMN])
&& $this->properties[Opt::NAME_CHANGE]) ? ~0
: ~Backend::SET_DISPLAYNAME;
$actions &= $this->properties[Opt::PASSWORD_CHANGE] ? ~0
: ~Backend::SET_PASSWORD;
return $actions;
}
/**
* Check if this backend is correctly set and can be enabled.
*
* @return bool TRUE if all necessary options for this backend
* are configured, FALSE otherwise.
*/
public function isConfigured()
{
return !empty($this->properties[DB::DATABASE])
&& !empty($this->properties[DB::DRIVER])
&& !empty($this->properties[DB::HOSTNAME])
&& !empty($this->properties[DB::USERNAME])
&& !empty($this->properties[DB::USER_TABLE])
&& !empty($this->properties[DB::USER_PASSWORD_COLUMN])
&& !empty($this->properties[Opt::CRYPTO_CLASS]);
}
}

107
lib/Cache.php Normal file
View File

@@ -0,0 +1,107 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL;
use OC\Memcache\NullCache;
use OCA\UserSQL\Constant\App;
use OCA\UserSQL\Constant\Opt;
use OCP\ICache;
use OCP\IConfig;
use OCP\ILogger;
/**
* Used to store key-value pairs in the cache memory.
* If there's no distributed cache available NULL cache is used.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Cache
{
/**
* @var ICache The cache instance.
*/
private $cache;
/**
* The default constructor. Initiates the cache memory.
*
* @param string $AppName The application name.
* @param IConfig $config The config instance.
* @param ILogger $logger The logger instance.
*/
public function __construct($AppName, IConfig $config, ILogger $logger)
{
$factory = \OC::$server->getMemCacheFactory();
$useCache = $config->getAppValue(
$AppName, Opt::USE_CACHE, App::FALSE_VALUE
);
if ($useCache === App::FALSE_VALUE) {
$this->cache = new NullCache();
} elseif ($factory->isAvailable()) {
$this->cache = $factory->createDistributed();
$logger->debug("Distributed cache initiated.", ["app" => $AppName]);
} else {
$logger->warning(
"There's no distributed cache available, fallback to null cache.",
["app" => $AppName]
);
$this->cache = new NullCache();
}
}
/**
* Fetch a value from the cache memory.
*
* @param string $key The cache value key.
*
* @return mixed|NULL Cached value or NULL if there's no value stored.
*/
public function get($key)
{
return $this->cache->get($key);
}
/**
* Store a value in the cache memory.
*
* @param string $key The cache value key.
* @param mixed $value The value to store.
* @param int $ttl (optional) TTL in seconds. Defaults to 1 hour.
*
* @return bool TRUE on success, FALSE otherwise.
*/
public function set($key, $value, $ttl = 3600)
{
return $this->cache->set($key, $value, $ttl);
}
/**
* Clear the cache of all entries.
*
* @return bool TRUE on success, FALSE otherwise.
*/
public function clear()
{
return $this->cache->clear();
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,33 +19,22 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm\Base;
namespace OCA\UserSQL\Constant;
/**
* Singleton pattern trait.
* The application constants.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
trait Singleton
final class App
{
private static $instance;
const FALSE_VALUE = "0";
const TRUE_VALUE = "1";
final private function __construct()
{
$this->init();
}
const HOME_QUERY = "query";
const HOME_STATIC = "static";
protected function init()
{
}
final public static function getInstance()
{
return isset(static::$instance)
? static::$instance
: static::$instance = new static;
}
final private function __clone()
{
}
const EMAIL_FORCE_NC = "force_nc";
const EMAIL_FORCE_SQL = "force_sql";
const EMAIL_INITIAL = "initial";
}

54
lib/Constant/DB.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Constant;
/**
* The database properties.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
final class DB
{
const DATABASE = "db.database";
const DRIVER = "db.driver";
const HOSTNAME = "db.hostname";
const PASSWORD = "db.password";
const USERNAME = "db.username";
const GROUP_TABLE = "db.table.group";
const USER_GROUP_TABLE = "db.table.user_group";
const USER_TABLE = "db.table.user";
const GROUP_ADMIN_COLUMN = "db.table.group.column.admin";
const GROUP_GID_COLUMN = "db.table.group.column.gid";
const GROUP_NAME_COLUMN = "db.table.group.column.name";
const USER_GROUP_GID_COLUMN = "db.table.user_group.column.gid";
const USER_GROUP_UID_COLUMN = "db.table.user_group.column.uid";
const USER_AVATAR_COLUMN = "db.table.user.column.avatar";
const USER_EMAIL_COLUMN = "db.table.user.column.email";
const USER_HOME_COLUMN = "db.table.user.column.home";
const USER_NAME_COLUMN = "db.table.user.column.name";
const USER_PASSWORD_COLUMN = "db.table.user.column.password";
const USER_UID_COLUMN = "db.table.user.column.uid";
}

38
lib/Constant/Opt.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Constant;
/**
* The option properties names.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
final class Opt
{
const CRYPTO_CLASS = "opt.crypto_class";
const EMAIL_SYNC = "opt.email_sync";
const HOME_LOCATION = "opt.home_location";
const HOME_MODE = "opt.home_mode";
const NAME_CHANGE = "opt.name_change";
const PASSWORD_CHANGE = "opt.password_change";
const USE_CACHE = "opt.use_cache";
}

47
lib/Constant/Query.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Constant;
/**
* The database query constants.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
final class Query
{
const BELONGS_TO_ADMIN = "belongs_to_admin";
const COUNT_GROUPS = "count_groups";
const COUNT_USERS = "count_users";
const FIND_GROUP = "find_group";
const FIND_GROUP_USERS = "find_group_users";
const FIND_GROUPS = "find_groups";
const FIND_USER = "find_user";
const FIND_USER_GROUPS = "find_user_groups";
const FIND_USERS = "find_users";
const SAVE_USER = "save_user";
const GID_PARAM = "gid";
const NAME_PARAM = "name";
const PASSWORD_PARAM = "password";
const SEARCH_PARAM = "search";
const UID_PARAM = "uid";
}

View File

@@ -0,0 +1,350 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Controller;
use Doctrine\DBAL\DBALException;
use Exception;
use OC\DatabaseException;
use OC\DB\Connection;
use OC\DB\ConnectionFactory;
use OCA\UserSQL\Cache;
use OCA\UserSQL\Constant\App;
use OCA\UserSQL\Platform\PlatformFactory;
use OCA\UserSQL\Properties;
use OCP\AppFramework\Controller;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IRequest;
/**
* The settings controller.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class SettingsController extends Controller
{
/**
* @var ILogger The logger instance.
*/
private $logger;
/**
* @var IL10N The localization service.
*/
private $localization;
/**
* @var Properties The properties array.
*/
private $properties;
/**
* @var Cache The cache instance.
*/
private $cache;
/**
* The default constructor.
*
* @param string $appName The application name.
* @param IRequest $request An instance of the request.
* @param ILogger $logger The logger instance.
* @param IL10N $localization The localization service.
* @param Properties $properties The properties array.
* @param Cache $cache The cache instance.
*/
public function __construct(
$appName, IRequest $request, ILogger $logger, IL10N $localization,
Properties $properties, Cache $cache
) {
parent::__construct($appName, $request);
$this->appName = $appName;
$this->logger = $logger;
$this->localization = $localization;
$this->properties = $properties;
$this->cache = $cache;
}
/**
* Verify the database connection parameters.
*
* @return array The request status.
*/
public function verifyDbConnection()
{
$this->logger->debug(
"Entering verifyDbConnection()", ["app" => $this->appName]
);
try {
$this->getConnection();
$this->logger->debug(
"Returning verifyDbConnection(): success",
["app" => $this->appName]
);
return [
"status" => "success",
"data" => [
"message" => $this->localization->t(
"Successfully connected to the database."
)
]
];
} catch (Exception $exception) {
$this->logger->debug(
"Returning verifyDbConnection(): error",
["app" => $this->appName]
);
return [
"status" => "error",
"data" => [
"message" => $this->localization->t(
"Error connecting to the database: "
) . $exception->getMessage()
]
];
}
}
/**
* Get the database connection instance.
*
* @return Connection The database connection instance.
* @throws DBALException On database connection problems.
* @throws DatabaseException Whenever no database driver is specified.
*/
private function getConnection()
{
$dbDriver = $this->request->getParam("db-driver");
$dbHostname = $this->request->getParam("db-hostname");
$dbDatabase = $this->request->getParam("db-database");
$dbUsername = $this->request->getParam("db-username");
$dbPassword = $this->request->getParam("db-password");
if (empty($dbDriver)) {
throw new DatabaseException("No database driver specified.");
}
$connectionFactory = new ConnectionFactory(
\OC::$server->getSystemConfig()
);
$parameters = [
"host" => $dbHostname,
"password" => $dbPassword,
"user" => $dbUsername,
"dbname" => $dbDatabase,
"tablePrefix" => ""
];
$connection = $connectionFactory->getConnection($dbDriver, $parameters);
$connection->executeQuery("SELECT 'user_sql'");
return $connection;
}
/**
* Save application properties.
*
* @return array The request status.
*/
public function saveProperties()
{
$this->logger->debug(
"Entering saveProperties()", ["app" => $this->appName]
);
$properties = $this->properties->getArray();
foreach ($properties as $key => $value) {
$reqValue = $this->request->getParam(str_replace(".", "-", $key));
$appValue = $this->properties[$key];
if ((!is_bool($appValue) && isset($reqValue)
&& $reqValue !== $appValue)
|| (is_bool($appValue) && isset($reqValue) !== $appValue)
) {
$value = isset($reqValue) ? $reqValue : App::FALSE_VALUE;
$this->properties[$key] = $value;
$this->logger->info(
"Property '$key' has been set to: " . $value,
["app" => $this->appName]
);
}
}
$this->logger->debug(
"Returning saveProperties(): success", ["app" => $this->appName]
);
return [
"status" => "success",
"data" => [
"message" => $this->localization->t(
"Properties has been saved."
)
]
];
}
/**
* Clear the application cache memory.
*
* @return array The request status.
*/
public function clearCache()
{
$this->logger->debug(
"Entering clearCache()", ["app" => $this->appName]
);
$this->cache->clear();
$this->logger->info(
"Cache memory has been cleared.", ["app" => $this->appName]
);
return [
"status" => "success",
"data" => [
"message" => $this->localization->t(
"Cache memory has been cleared."
)
]
];
}
/**
* Autocomplete for table select options.
*
* @return array The database table list.
*/
public function tableAutocomplete()
{
$this->logger->debug(
"Entering tableAutocomplete()", ["app" => $this->appName]
);
try {
$connection = $this->getConnection();
$platform = PlatformFactory::getPlatform($connection);
$tables = $platform->getTables();
$this->logger->debug(
"Returning tableAutocomplete(): count(" . count($tables) . ")",
["app" => $this->appName]
);
return $tables;
} catch (Exception $e) {
$this->logger->logException($e);
return [];
}
}
/**
* Autocomplete for column select options - user table.
*
* @return array The database table's column list.
*/
public function userTableAutocomplete()
{
$this->logger->debug(
"Entering userTableAutocomplete()", ["app" => $this->appName]
);
$columns = $this->columnAutocomplete("db-table-user");
$this->logger->debug(
"Returning userTableAutocomplete(): count(" . count($columns) . ")",
["app" => $this->appName]
);
return $columns;
}
/**
* Autocomplete for column select options.
*
* @param string $table The table's form ID.
*
* @return array The table's column list.
*/
private function columnAutocomplete($table)
{
try {
$connection = $this->getConnection();
$platform = PlatformFactory::getPlatform($connection);
$columns = $platform->getColumns(
$this->request->getParam($table)
);
return $columns;
} catch (Exception $e) {
$this->logger->logException($e);
return [];
}
}
/**
* Autocomplete for column select options - user_group table.
*
* @return array The database table's column list.
*/
public function userGroupTableAutocomplete()
{
$this->logger->debug(
"Entering userGroupTableAutocomplete()", ["app" => $this->appName]
);
$columns = $this->columnAutocomplete("db-table-user_group");
$this->logger->debug(
"Returning userGroupTableAutocomplete(): count(" . count($columns)
. ")", ["app" => $this->appName]
);
return $columns;
}
/**
* Autocomplete for column select options - group table.
*
* @return array The database table's column list.
*/
public function groupTableAutocomplete()
{
$this->logger->debug(
"Entering groupTableAutocomplete()", ["app" => $this->appName]
);
$columns = $this->columnAutocomplete("db-table-group");
$this->logger->debug(
"Returning groupTableAutocomplete(): count(" . count($columns)
. ")", ["app" => $this->appName]
);
return $columns;
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Crypto;
use OCP\IL10N;
/**
* The abstract password algorithm class.
* Each algorithm should extend this class, as it provides very base
* functionality which seems to be necessary for every implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
abstract class AbstractAlgorithm implements IPasswordAlgorithm
{
/**
* @var IL10N The localization service.
*/
private $localization;
/**
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function __construct(IL10N $localization)
{
$this->localization = $localization;
}
/**
* @inheritdoc
*/
public function getVisibleName()
{
return $this->localization->t($this->getAlgorithmName());
}
/**
* Get the algorithm name.
*
* @return string The algorithm name.
*/
protected abstract function getAlgorithmName();
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return hash_equals($dbHash, $this->getPasswordHash($password));
}
/**
* @inheritdoc
*/
public abstract function getPasswordHash($password);
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,24 +19,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm\Base;
namespace OCA\UserSQL\Crypto;
/**
* Implements standard Unix DES-based algorithm or
* alternative algorithms that may be available on the system.
* @see crypt()
* Abstract Unix Crypt hashing implementation.
* The hashing algorithm depends on the chosen salt.
*
* @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me>
*/
abstract class BaseCrypt implements HashAlgorithm
abstract class AbstractCrypt extends AbstractAlgorithm
{
use Singleton;
const SALT_ALPHABET = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/**
* @inheritdoc
* The chars used in the salt.
*/
abstract public function getVisibleName();
const SALT_ALPHABET = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/**
* @inheritdoc
@@ -53,8 +52,12 @@ abstract class BaseCrypt implements HashAlgorithm
}
/**
* Generate salt for hashing algorithm.
* @return string
* Generate a salt string for the hashing algorithm.
*
* @return string The salt string.
*/
protected abstract function getSalt();
protected function getSalt()
{
return "";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,25 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
use OCP\IL10N;
/**
* Cleartext password implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Cleartext implements HashAlgorithm
class Cleartext extends AbstractAlgorithm
{
use Singleton;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "Cleartext";
parent::__construct($localization);
}
/**
@@ -49,8 +51,8 @@ class Cleartext implements HashAlgorithm
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
protected function getAlgorithmName()
{
return hash_equals($dbHash, $password);
return "Cleartext";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,35 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
use OCA\UserSQL\HashAlgorithm\Base\Utils;
use OCP\IL10N;
/**
* Courier MD5 hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CourierMD5 implements HashAlgorithm
class CourierMD5 extends AbstractAlgorithm
{
use Singleton;
use Utils;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "Courier base64-encoded MD5";
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return hash_equals($dbHash, $this->getPasswordHash($password));
parent::__construct($localization);
}
/**
@@ -53,6 +45,14 @@ class CourierMD5 implements HashAlgorithm
*/
public function getPasswordHash($password)
{
return '{MD5}' . self::hexToBase64(md5($password));
return '{MD5}' . Utils::hexToBase64(md5($password));
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Courier base64-encoded MD5";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,33 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
use OCP\IL10N;
/**
* Courier MD5 RAW hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CourierMD5Raw implements HashAlgorithm
class CourierMD5Raw extends AbstractAlgorithm
{
use Singleton;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "Courier hexadecimal MD5";
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return hash_equals($dbHash, $this->getPasswordHash($password));
parent::__construct($localization);
}
/**
@@ -53,4 +47,12 @@ class CourierMD5Raw implements HashAlgorithm
{
return '{MD5RAW}' . md5($password);
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Courier hexadecimal MD5";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,35 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
use OCA\UserSQL\HashAlgorithm\Base\Utils;
use OCP\IL10N;
/**
* Courier SHA1 hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CourierSHA1 implements HashAlgorithm
class CourierSHA1 extends AbstractAlgorithm
{
use Singleton;
use Utils;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "Courier base64-encoded SHA1";
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return hash_equals($dbHash, $this->getPasswordHash($password));
parent::__construct($localization);
}
/**
@@ -53,6 +45,14 @@ class CourierSHA1 implements HashAlgorithm
*/
public function getPasswordHash($password)
{
return '{SHA}' . self::hexToBase64(sha1($password));
return '{SHA}' . Utils::hexToBase64(sha1($password));
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Courier base64-encoded SHA1";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,35 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
use OCA\UserSQL\HashAlgorithm\Base\Utils;
use OCP\IL10N;
/**
* Courier SHA256 hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CourierSHA256 implements HashAlgorithm
class CourierSHA256 extends AbstractAlgorithm
{
use Singleton;
use Utils;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "Courier base64-encoded SHA256";
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return hash_equals($dbHash, $this->getPasswordHash($password));
parent::__construct($localization);
}
/**
@@ -53,6 +45,14 @@ class CourierSHA256 implements HashAlgorithm
*/
public function getPasswordHash($password)
{
return '{SHA256}' . self::hexToBase64(hash('sha256', $password));
return '{SHA256}' . Utils::hexToBase64(hash('sha256', $password));
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Courier base64-encoded SHA256";
}
}

59
lib/Crypto/Crypt.php Normal file
View File

@@ -0,0 +1,59 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Crypto;
use OCP\IL10N;
/**
* Unix Crypt hashing implementation.
*
* @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Crypt extends AbstractCrypt
{
/**
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function __construct(IL10N $localization)
{
parent::__construct($localization);
}
/**
* @inheritdoc
*/
public function getPasswordHash($password)
{
return password_hash($password, PASSWORD_DEFAULT);
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Unix (Crypt)";
}
}

View File

@@ -0,0 +1,97 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Crypto;
use OCP\IL10N;
/**
* Argon2 Crypt hashing implementation.
*
* @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptArgon2 extends AbstractAlgorithm
{
/**
* @var int Maximum memory (in bytes) that may be used to compute.
*/
private $memoryCost;
/**
* @var int Maximum amount of time it may take to compute.
*/
private $timeCost;
/**
* @var int Number of threads to use for computing.
*/
private $threads;
/**
* The class constructor.
*
* @param IL10N $localization The localization service.
* @param int $memoryCost Maximum memory (in bytes) that may be used
* to compute.
* @param int $timeCost Maximum amount of time it may take to compute.
* @param int $threads Number of threads to use for computing.
*/
public function __construct(
IL10N $localization,
$memoryCost = PASSWORD_ARGON2_DEFAULT_MEMORY_COST,
$timeCost = PASSWORD_ARGON2_DEFAULT_TIME_COST,
$threads = PASSWORD_ARGON2_DEFAULT_THREADS
) {
parent::__construct($localization);
$this->memoryCost = $memoryCost;
$this->timeCost = $timeCost;
$this->threads = $threads;
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return password_verify($password, $dbHash);
}
/**
* @inheritdoc
*/
public function getPasswordHash($password)
{
return password_hash(
$password, PASSWORD_ARGON2I, [
"memory_cost" => $this->memoryCost,
"time_cost" => $this->timeCost,
"threads" => $this->threads
]
);
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Argon2 (Crypt)";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,25 +19,34 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
use OCP\IL10N;
/**
* Blowfish Crypt hashing implementation.
*
* @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptBlowfish implements HashAlgorithm
class CryptBlowfish extends AbstractAlgorithm
{
use Singleton;
/**
* @var int Denotes the algorithmic cost that should be used.
*/
private $cost;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
* @param int $cost Denotes the algorithmic cost that should
* be used.
*/
public function getVisibleName()
public function __construct(IL10N $localization, $cost = 10)
{
return "Blowfish (Crypt)";
parent::__construct($localization);
$this->cost = $cost;
}
/**
@@ -51,7 +62,18 @@ class CryptBlowfish implements HashAlgorithm
*/
public function getPasswordHash($password)
{
// TODO - add support for options: cost.
return password_hash($password, PASSWORD_BCRYPT);
return password_hash(
$password, PASSWORD_BCRYPT, ["cost" => $this->cost]
);
}
/**
* Get the algorithm name.
*
* @return string The algorithm name.
*/
protected function getAlgorithmName()
{
return "Blowfish (Crypt)";
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Crypto;
use OCP\IL10N;
/**
* Extended DES Crypt hashing implementation.
*
* @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptExtendedDES extends AbstractCrypt
{
/**
* @var int The number of iterations.
*/
private $iterationCount;
/**
* The class constructor.
*
* @param IL10N $localization The localization service.
* @param int $iterationCount The number of iterations.
*/
public function __construct(IL10N $localization, $iterationCount = 1000)
{
parent::__construct($localization);
$this->iterationCount = $iterationCount;
}
/**
* @inheritdoc
*/
protected function getSalt()
{
return self::encodeIterationCount($this->iterationCount)
. Utils::randomString(4, self::SALT_ALPHABET);
}
/**
* Get the number of iterations as describe below.
* The 4 bytes of iteration count are encoded as printable characters,
* 6 bits per character, least significant character first.
* The values 0 to 63 are encoded as "./0-9A-Za-z".
*
* @param int $number The number of iterations.
*
* @return string
*/
private static function encodeIterationCount($number)
{
$alphabet = str_split(self::SALT_ALPHABET);
$chars = array();
$base = sizeof($alphabet);
while ($number) {
$rem = $number % $base;
$number = (int)($number / $base);
$arr[] = $alphabet[$rem];
}
return str_pad(implode($chars), 4, ".", STR_PAD_RIGHT);
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Extended DES (Crypt)";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,25 +19,26 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\BaseCrypt;
use OCA\UserSQL\HashAlgorithm\Base\Utils;
use OCP\IL10N;
/**
* MD5 Crypt hashing implementation.
*
* @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptMD5 extends BaseCrypt
class CryptMD5 extends AbstractCrypt
{
use Utils;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "MD5 (Crypt)";
parent::__construct($localization);
}
/**
@@ -43,6 +46,14 @@ class CryptMD5 extends BaseCrypt
*/
protected function getSalt()
{
return "$1$" . self::randomString(8, self::SALT_ALPHABET) . "$";
return "$1$" . Utils::randomString(8, self::SALT_ALPHABET) . "$";
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "MD5 (Crypt)";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,25 +19,34 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\BaseCrypt;
use OCA\UserSQL\HashAlgorithm\Base\Utils;
use OCP\IL10N;
/**
* SHA256 Crypt hashing implementation.
*
* @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptSHA256 extends BaseCrypt
class CryptSHA256 extends AbstractCrypt
{
use Utils;
/**
* @var int The number of rounds.
*/
private $rounds;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
* @param int $rounds The number of rounds.
* This value must be between 1000 and 999999999.
*/
public function getVisibleName()
public function __construct(IL10N $localization, $rounds = 5000)
{
return "SHA256 (Crypt)";
parent::__construct($localization);
$this->rounds = $rounds;
}
/**
@@ -43,7 +54,16 @@ class CryptSHA256 extends BaseCrypt
*/
protected function getSalt()
{
// TODO - add support for options: rounds.
return "$5\$rounds=5000$" . self::randomString(16, self::SALT_ALPHABET) . "$";
return "$5\$rounds=" . $this->rounds . "$" . Utils::randomString(
16, self::SALT_ALPHABET
) . "$";
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "SHA256 (Crypt)";
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Crypto;
use OCP\IL10N;
/**
* SHA512 Crypt hashing implementation.
*
* @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptSHA512 extends AbstractCrypt
{
/**
* @var int The number of rounds.
*/
private $rounds;
/**
* The class constructor.
*
* @param IL10N $localization The localization service.
* @param int $rounds The number of rounds.
* This value must be between 1000 and 999999999.
*/
public function __construct(IL10N $localization, $rounds = 5000)
{
parent::__construct($localization);
$this->rounds = $rounds;
}
/**
* @inheritdoc
*/
protected function getSalt()
{
return "$6\$rounds=" . $this->rounds . "$" . Utils::randomString(
16, self::SALT_ALPHABET
) . "$";
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "SHA512 (Crypt)";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,25 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\BaseCrypt;
use OCA\UserSQL\HashAlgorithm\Base\Utils;
use OCP\IL10N;
/**
* Standard DES Crypt hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptStandardDES extends BaseCrypt
class CryptStandardDES extends AbstractCrypt
{
use Utils;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "Standard DES (Crypt)";
parent::__construct($localization);
}
/**
@@ -43,6 +45,14 @@ class CryptStandardDES extends BaseCrypt
*/
protected function getSalt()
{
return self::randomString(2, self::SALT_ALPHABET);
return Utils::randomString(2, self::SALT_ALPHABET);
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Standard DES (Crypt)";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,24 +19,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm\Base;
namespace OCA\UserSQL\Crypto;
/**
* Interface which defines all function required by a hash algorithm.
* Please note that this interface must be implemented by every hash function supported in this app.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
interface HashAlgorithm
interface IPasswordAlgorithm
{
/**
* Used by reflection to get the class instance.
* @return HashAlgorithm
*/
public static function getInstance();
/**
* Get the hash algorithm name.
* This name is visible in the admin panel.
*
* @return string
*/
public function getVisibleName();
@@ -42,15 +40,19 @@ interface HashAlgorithm
/**
* Hash given password.
* This value is stored in the database, when the password is changed.
*
* @param String $password The new password.
*
* @return boolean True if the password was hashed successfully, false otherwise.
*/
public function getPasswordHash($password);
/**
* Check password given by the user against hash stored in the database.
*
* @param String $password Password given by the user.
* @param String $dbHash Password hash stored in the database.
* @param String $dbHash Password hash stored in the database.
*
* @return boolean True if the password is correct, false otherwise.
*/
public function checkPassword($password, $dbHash);

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,27 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
use OCA\UserSQL\HashAlgorithm\Base\Utils;
use OCP\IL10N;
/**
* Joomla hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Joomla implements HashAlgorithm
class Joomla extends AbstractAlgorithm
{
use Singleton;
use Utils;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "Joomla MD5 Encryption";
parent::__construct($localization);
}
/**
@@ -45,8 +45,12 @@ class Joomla implements HashAlgorithm
*/
public function getPasswordHash($password)
{
return md5($password . ":" . self::randomString(32,
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"));
return md5(
$password . ":" . Utils::randomString(
32,
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
)
);
}
/**
@@ -68,4 +72,12 @@ class Joomla implements HashAlgorithm
$pwHash .= ":" . $salt;
return $pwHash;
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "Joomla MD5 Encryption";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,33 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
use OCP\IL10N;
/**
* MD5 hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class MD5 implements HashAlgorithm
class MD5 extends AbstractAlgorithm
{
use Singleton;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "MD5";
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return hash_equals($dbHash, $this->getPasswordHash($password));
parent::__construct($localization);
}
/**
@@ -53,4 +47,12 @@ class MD5 implements HashAlgorithm
{
return md5($password);
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "MD5";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,33 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
use OCP\IL10N;
/**
* SHA1 hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class SHA1 implements HashAlgorithm
class SHA1 extends AbstractAlgorithm
{
use Singleton;
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "SHA1";
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return hash_equals($dbHash, $this->getPasswordHash($password));
parent::__construct($localization);
}
/**
@@ -53,4 +47,12 @@ class SHA1 implements HashAlgorithm
{
return sha1($password);
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "SHA1";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,23 +19,35 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm\Base;
namespace OCA\UserSQL\Crypto;
use OCP\IL10N;
/**
* SSHA* hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
abstract class SSHA implements HashAlgorithm
abstract class SSHA extends AbstractAlgorithm
{
use Singleton;
use Utils;
/**
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function __construct(IL10N $localization)
{
parent::__construct($localization);
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
$saltedPassword = base64_decode(preg_replace("/" . $this->getPrefix() . "/i", "", $dbHash));
$saltedPassword = base64_decode(
preg_replace("/" . $this->getPrefix() . "/i", "", $dbHash)
);
$salt = substr($saltedPassword, -(strlen($saltedPassword) - 32));
$hash = self::ssha($password, $salt);
@@ -42,23 +56,29 @@ abstract class SSHA implements HashAlgorithm
/**
* Get hash prefix eg. {SSHA256}.
* @return string
*
* @return string The hash prefix.
*/
public abstract function getPrefix();
/**
* Encrypt using SSHA256 algorithm
* Encrypt using SSHA* algorithm.
*
* @param string $password The password.
* @param string $salt The salt to use.
* @return string The hashed password, prefixed by {SSHA256}.
* @param string $salt The salt to use.
*
* @return string The hashed password, prefixed by {SSHA*}.
*/
private function ssha($password, $salt)
{
return $this->getPrefix() . base64_encode(hash($this->getAlgorithm(), $password . $salt, true) . $salt);
return $this->getPrefix() . base64_encode(
hash($this->getAlgorithm(), $password . $salt, true) . $salt
);
}
/**
* Get algorithm used by the hash() function.
*
* @see hash()
* @return string
*/
@@ -69,7 +89,10 @@ abstract class SSHA implements HashAlgorithm
*/
public function getPasswordHash($password)
{
return self::ssha($password,
self::randomString(32, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"));
return self::ssha(
$password, Utils::randomString(
32, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
)
);
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,22 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\SSHA;
use OCP\IL10N;
/**
* SSHA256 hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class SSHA256 extends SSHA
{
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "SSHA256";
parent::__construct($localization);
}
/**
@@ -50,4 +55,12 @@ class SSHA256 extends SSHA
{
return "sha256";
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "SSHA256";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,22 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
namespace OCA\UserSQL\Crypto;
use OCA\UserSQL\HashAlgorithm\Base\SSHA;
use OCP\IL10N;
/**
* SSHA512 hashing implementation.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class SSHA512 extends SSHA
{
/**
* @inheritdoc
* The class constructor.
*
* @param IL10N $localization The localization service.
*/
public function getVisibleName()
public function __construct(IL10N $localization)
{
return "SSHA512";
parent::__construct($localization);
}
/**
@@ -50,4 +55,12 @@ class SSHA512 extends SSHA
{
return "sha512";
}
/**
* @inheritdoc
*/
protected function getAlgorithmName()
{
return "SSHA512";
}
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,22 +19,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm\Base;
namespace OCA\UserSQL\Crypto;
/**
* Cryptographic utilities trait.
* Cryptographic utilities.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
trait Utils
final class Utils
{
/**
* Convert hexadecimal message to its base64 form.
* @param $hex string Hexadecimal encoded message.
* @return string Same message encoded in base64.
*
* @param $hex string The hexadecimal encoded message.
*
* @return string The same message encoded in base64.
*/
private static function hexToBase64($hex)
public static function hexToBase64($hex)
{
$hexChr = '';
$hexChr = "";
foreach (str_split($hex, 2) as $hexPair) {
$hexChr .= chr(hexdec($hexPair));
}
@@ -41,14 +46,16 @@ trait Utils
/**
* Generate random string from given alphabet.
* @param $length int Output string length.
* @param $alphabet string Output string alphabet.
*
* @param $length int The output string length.
* @param $alphabet string The output string alphabet.
*
* @return string Random string from given alphabet.
*/
private static function randomString($length, $alphabet)
public static function randomString($length, $alphabet)
{
$string = "";
for ($i = 0; $i != $length; ++$i) {
for ($idx = 0; $idx != $length; ++$idx) {
$string .= $alphabet[mt_rand(0, strlen($alphabet) - 1)];
}
return $string;

View File

@@ -1,59 +0,0 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
/**
* Implements standard Unix DES-based algorithm or
* alternative algorithms that may be available on the system.
* This implementation does not support password changing.
* @see crypt()
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Crypt implements HashAlgorithm
{
use Singleton;
/**
* @inheritdoc
*/
public function getVisibleName()
{
return "Crypt (Unix)";
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return hash_equals($dbHash, crypt($password, $dbHash));
}
/**
* @inheritdoc
*/
public function getPasswordHash($password)
{
return password_hash($password, PASSWORD_DEFAULT);
}
}

View File

@@ -1,57 +0,0 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\Singleton;
/**
* Argon2 Crypt hashing implementation.
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptArgon2 implements HashAlgorithm
{
use Singleton;
/**
* @inheritdoc
*/
public function getVisibleName()
{
return "Argon2 (Crypt)";
}
/**
* @inheritdoc
*/
public function checkPassword($password, $dbHash)
{
return password_verify($password, $dbHash);
}
/**
* @inheritdoc
*/
public function getPasswordHash($password)
{
// TODO - add support for options: memory_cost, time_cost, threads.
return password_hash($password, PASSWORD_ARGON2I);
}
}

View File

@@ -1,63 +0,0 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\BaseCrypt;
use OCA\UserSQL\HashAlgorithm\Base\Utils;
/**
* Extended DES Crypt hashing implementation.
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptExtendedDES extends BaseCrypt
{
use Utils;
/**
* @inheritdoc
*/
public function getVisibleName()
{
return "Extended DES (Crypt)";
}
/**
* @inheritdoc
*/
protected function getSalt()
{
// TODO - add support for options: iteration_count.
return self::base64IntEncode(1000) . self::randomString(4, self::SALT_ALPHABET);
}
private static function base64IntEncode($number)
{
$alphabet = str_split(self::SALT_ALPHABET);
$chars = array();
$base = sizeof($alphabet);
while ($number) {
$rem = $number % $base;
$number = (int)($number / $base);
$arr[] = $alphabet[$rem];
}
$string = implode($chars);
return str_pad($string, 4, '.', STR_PAD_RIGHT);
}
}

43
lib/Model/Group.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Model;
/**
* The group entity.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Group
{
/**
* @var string The GID (group name).
*/
public $gid;
/**
* @var string The group's display name.
*/
public $name;
/**
* @var bool Whether it is an admin group.
*/
public $admin;
}

View File

@@ -1,7 +1,9 @@
<?php
/**
* Nextcloud - user_sql
* Copyright (C) 2018 Marcin Łojewski <dev@mlojewski.me>
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,33 +19,37 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\HashAlgorithm;
use OCA\UserSQL\HashAlgorithm\Base\BaseCrypt;
use OCA\UserSQL\HashAlgorithm\Base\Utils;
namespace OCA\UserSQL\Model;
/**
* SHA512 Crypt hashing implementation.
* The user entity.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class CryptSHA512 extends BaseCrypt
class User
{
use Utils;
/**
* @inheritdoc
* @var string The UID (username).
*/
public function getVisibleName()
{
return "SHA512 (Crypt)";
}
public $uid;
/**
* @inheritdoc
* @var string The user's email address.
*/
protected function getSalt()
{
// TODO - add support for options: rounds.
return "$5\$rounds=5000$" . self::randomString(16, self::SALT_ALPHABET) . "$";
}
public $email;
/**
* @var string The user's display name.
*/
public $name;
/**
* @var string The user's password (hash).
*/
public $password;
/**
* @var string The user's home location.
*/
public $home;
/**
* @var bool Can user change its avatar.
*/
public $avatar;
}

View File

@@ -1,268 +0,0 @@
<?php
#
# Portable PHP password hashing framework.
#
# Version 0.3 / genuine.
#
# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
# the public domain. Revised in subsequent years, still public domain.
#
# There's absolutely no warranty.
#
# The homepage URL for this framework is:
#
# http://www.openwall.com/phpass/
#
# Please be sure to update the Version line if you edit this file in any way.
# It is suggested that you leave the main version number intact, but indicate
# your project name (after the slash) and add your own revision information.
#
# Please do not change the "private" password hashing method implemented in
# here, thereby making your hashes incompatible. However, if you must, please
# change the hash type identifier (the "$P$") to something different.
#
# Obviously, since this code is in the public domain, the above are not
# requirements (there can be none), but merely suggestions.
#
class PasswordHash
{
var $itoa64;
var $iteration_count_log2;
var $portable_hashes;
var $random_state;
function __construct($iteration_count_log2, $portable_hashes)
{
$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
$iteration_count_log2 = 8;
}
$this->iteration_count_log2 = $iteration_count_log2;
$this->portable_hashes = $portable_hashes;
$this->random_state = microtime();
if (function_exists('getmypid')) {
$this->random_state .= getmypid();
}
}
function get_random_bytes($count)
{
$output = '';
if (is_readable('/dev/urandom') &&
($fh = @fopen('/dev/urandom', 'rb'))) {
$output = fread($fh, $count);
fclose($fh);
}
if (strlen($output) < $count) {
$output = '';
for ($i = 0; $i < $count; $i += 16) {
$this->random_state =
md5(microtime() . $this->random_state);
$output .=
pack('H*', md5($this->random_state));
}
$output = substr($output, 0, $count);
}
return $output;
}
function encode64($input, $count)
{
$output = '';
$i = 0;
do {
$value = ord($input[$i++]);
$output .= $this->itoa64[$value & 0x3f];
if ($i < $count) {
$value |= ord($input[$i]) << 8;
}
$output .= $this->itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count) {
break;
}
if ($i < $count) {
$value |= ord($input[$i]) << 16;
}
$output .= $this->itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count) {
break;
}
$output .= $this->itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
function gensalt_private($input)
{
$output = '$P$';
$output .= $this->itoa64[min($this->iteration_count_log2 +
((PHP_VERSION >= '5') ? 5 : 3), 30)];
$output .= $this->encode64($input, 6);
return $output;
}
function crypt_private($password, $setting)
{
$output = '*0';
if (substr($setting, 0, 2) === $output) {
$output = '*1';
}
$id = substr($setting, 0, 3);
# We use "$P$", phpBB3 uses "$H$" for the same thing
if ($id !== '$P$' && $id !== '$H$') {
return $output;
}
$count_log2 = strpos($this->itoa64, $setting[3]);
if ($count_log2 < 7 || $count_log2 > 30) {
return $output;
}
$count = 1 << $count_log2;
$salt = substr($setting, 4, 8);
if (strlen($salt) !== 8) {
return $output;
}
# We're kind of forced to use MD5 here since it's the only
# cryptographic primitive available in all versions of PHP
# currently in use. To implement our own low-level crypto
# in PHP would result in much worse performance and
# consequently in lower iteration counts and hashes that are
# quicker to crack (by non-PHP code).
if (PHP_VERSION >= '5') {
$hash = md5($salt . $password, true);
do {
$hash = md5($hash . $password, true);
} while (--$count);
} else {
$hash = pack('H*', md5($salt . $password));
do {
$hash = pack('H*', md5($hash . $password));
} while (--$count);
}
$output = substr($setting, 0, 12);
$output .= $this->encode64($hash, 16);
return $output;
}
function gensalt_extended($input)
{
$count_log2 = min($this->iteration_count_log2 + 8, 24);
# This should be odd to not reveal weak DES keys, and the
# maximum valid value is (2**24 - 1) which is odd anyway.
$count = (1 << $count_log2) - 1;
$output = '_';
$output .= $this->itoa64[$count & 0x3f];
$output .= $this->itoa64[($count >> 6) & 0x3f];
$output .= $this->itoa64[($count >> 12) & 0x3f];
$output .= $this->itoa64[($count >> 18) & 0x3f];
$output .= $this->encode64($input, 3);
return $output;
}
function gensalt_blowfish($input)
{
# This one needs to use a different order of characters and a
# different encoding scheme from the one in encode64() above.
# We care because the last character in our encoded string will
# only represent 2 bits. While two known implementations of
# bcrypt will happily accept and correct a salt string which
# has the 4 unused bits set to non-zero, we do not want to take
# chances and we also do not want to waste an additional byte
# of entropy.
$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$output = '$2a$';
$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
$output .= '$';
$i = 0;
do {
$c1 = ord($input[$i++]);
$output .= $itoa64[$c1 >> 2];
$c1 = ($c1 & 0x03) << 4;
if ($i >= 16) {
$output .= $itoa64[$c1];
break;
}
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 4;
$output .= $itoa64[$c1];
$c1 = ($c2 & 0x0f) << 2;
$c2 = ord($input[$i++]);
$c1 |= $c2 >> 6;
$output .= $itoa64[$c1];
$output .= $itoa64[$c2 & 0x3f];
} while (1);
return $output;
}
function HashPassword($password)
{
$random = '';
if (CRYPT_BLOWFISH === 1 && !$this->portable_hashes) {
$random = $this->get_random_bytes(16);
$hash =
crypt($password, $this->gensalt_blowfish($random));
if (strlen($hash) === 60) {
return $hash;
}
}
if (CRYPT_EXT_DES === 1 && !$this->portable_hashes) {
if (strlen($random) < 3) {
$random = $this->get_random_bytes(3);
}
$hash =
crypt($password, $this->gensalt_extended($random));
if (strlen($hash) === 20) {
return $hash;
}
}
if (strlen($random) < 6) {
$random = $this->get_random_bytes(6);
}
$hash =
$this->crypt_private($password,
$this->gensalt_private($random));
if (strlen($hash) === 34) {
return $hash;
}
# Returning '*' on error is safe here, but would _not_ be safe
# in a crypt(3)-like function used _both_ for generating new
# hashes and for validating passwords against existing hashes.
return '*';
}
function CheckPassword($password, $stored_hash)
{
$hash = $this->crypt_private($password, $stored_hash);
if ($hash[0] === '*') {
$hash = crypt($password, $stored_hash);
}
return $hash === $stored_hash;
}
}

View File

@@ -0,0 +1,135 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Platform;
use Doctrine\DBAL\DBALException;
use OC\DB\Connection;
/**
* Database platform tools.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
abstract class AbstractPlatform
{
/**
* @var Connection The database connection.
*/
protected $connection;
/**
* The class constructor.
*
* @param Connection $connection The database connection.
*/
public function __construct(Connection $connection)
{
$this->connection = $connection;
}
/**
* Get all the tables defined in the database.
*
* @param bool $schemaPrefix Show schema name in the results.
*
* @return array Array with table names.
* @throws DBALException On a database exception.
*/
public function getTables($schemaPrefix = false)
{
$platform = $this->connection->getDatabasePlatform();
$queryTables = $platform->getListTablesSQL();
$queryViews = $platform->getListViewsSQL(
$this->connection->getDatabase()
);
$tables = array();
$result = $this->connection->executeQuery($queryTables);
while ($row = $result->fetch()) {
$name = $this->getTableName($row, $schemaPrefix);
$tables[] = $name;
}
$result = $this->connection->executeQuery($queryViews);
while ($row = $result->fetch()) {
$name = $this->getViewName($row, $schemaPrefix);
$tables[] = $name;
}
return $tables;
}
/**
* Get a table name from a query result row.
*
* @param array $row The query result row.
* @param string $schema Put schema name in the result.
*
* @return string The table name retrieved from the row.
*/
protected abstract function getTableName($row, $schema);
/**
* Get a view name from a query result row.
*
* @param array $row The query result row.
* @param string $schema Put schema name in the result.
*
* @return string The view name retrieved from the row.
*/
protected abstract function getViewName($row, $schema);
/**
* Get all the columns defined in the table.
*
* @param string $table The table name.
*
* @return array Array with column names.
* @throws DBALException On a database exception.
*/
public function getColumns($table)
{
$platform = $this->connection->getDatabasePlatform();
$query = $platform->getListTableColumnsSQL($table);
$result = $this->connection->executeQuery($query);
$columns = array();
while ($row = $result->fetch()) {
$name = $this->getColumnName($row);
$columns[] = $name;
}
return $columns;
}
/**
* Get a column name from a query result row.
*
* @param array $row The query result row.
*
* @return string The column name retrieved from the row.
*/
protected abstract function getColumnName($row);
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Platform;
use OC\DB\Connection;
/**
* MySQL database platform.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class MySQLPlatform extends AbstractPlatform
{
/**
* The class constructor.
*
* @param Connection $connection The database connection.
*/
public function __construct(Connection $connection)
{
parent::__construct($connection);
}
/**
* @inheritdoc
*/
protected function getViewName($row, $schema)
{
return $row["TABLE_NAME"];
}
/**
* @inheritdoc
*/
protected function getTableName($row, $schema)
{
return $row["Tables_in_" . $this->connection->getDatabase()];
}
/**
* @inheritdoc
*/
protected function getColumnName($row)
{
return $row["Field"];
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Platform;
use OC\DB\Connection;
/**
* Factory for the database platform class instance.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class PlatformFactory
{
/**
* Get the database platform.
*
* @param Connection $connection The database connection.
*
* @return AbstractPlatform The database platform.
*/
public static function getPlatform(Connection $connection)
{
switch ($connection->getDriver()->getName()) {
case "pdo_mysql":
return new MySQLPlatform($connection);
case "pdo_pgsql":
return new PostgreSQLPlatform($connection);
default:
throw new \InvalidArgumentException(
"Unknown database driver: " . $connection->getDriver()->getName(
)
);
}
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Platform;
use OC\DB\Connection;
/**
* PostgreSQL database platform.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class PostgreSQLPlatform extends AbstractPlatform
{
/**
* The class constructor.
*
* @param Connection $connection The database connection.
*/
public function __construct(Connection $connection)
{
parent::__construct($connection);
}
/**
* @inheritdoc
*/
protected function getViewName($row, $schema)
{
$schema ? ($row["schemaname"] . "." . $row["viewname"])
: $row["viewname"];
}
/**
* @inheritdoc
*/
protected function getTableName($row, $schema)
{
$schema ? ($row["schema_name"] . "." . $row["table_name"])
: $row["table_name"];
}
/**
* @inheritdoc
*/
protected function getColumnName($row)
{
return $row["field"];
}
}

211
lib/Properties.php Normal file
View File

@@ -0,0 +1,211 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL;
use OCA\UserSQL\Constant\App;
use OCA\UserSQL\Constant\DB;
use OCA\UserSQL\Constant\Opt;
use OCP\IConfig;
use OCP\ILogger;
/**
* Store and retrieve application properties.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Properties implements \ArrayAccess
{
/**
* @var string The cache key name.
*/
const CACHE_KEY = "Properties_data";
/**
* @var string The application name.
*/
private $appName;
/**
* @var IConfig The config instance.
*/
private $config;
/**
* @var ILogger The logger instance.
*/
private $logger;
/**
* @var Cache The cache instance.
*/
private $cache;
/**
* @var array The properties array.
*/
private $data;
/**
* The default constructor.
*
* @param string $AppName The application name.
* @param IConfig $config The config instance.
* @param ILogger $logger The logger instance.
* @param Cache $cache The cache instance.
*/
public function __construct(
$AppName, IConfig $config, ILogger $logger, Cache $cache
) {
$this->appName = $AppName;
$this->config = $config;
$this->logger = $logger;
$this->cache = $cache;
$this->loadProperties();
}
/**
* Load the application properties.
*
* First the values are fetched from the cache memory.
* If these are not available, the database values are fetched.
*/
private function loadProperties()
{
$this->data = $this->cache->get(self::CACHE_KEY);
if (!is_null($this->data)) {
return;
}
$params = $this->getParameterArray();
$this->data = [];
foreach ($params as $param) {
$value = $this->config->getAppValue($this->appName, $param, null);
if ($value === App::FALSE_VALUE) {
$value = false;
} elseif ($value === App::TRUE_VALUE) {
$value = true;
}
$this->data[$param] = $value;
}
$this->store();
$this->logger->debug(
"The application properties has been loaded.",
["app" => $this->appName]
);
}
/**
* Return an array with all supported parameters.
*
* @return array Array containing strings of the parameters.
*/
private function getParameterArray()
{
$params = [];
foreach ([DB::class, Opt::class] as $class) {
try {
$reflection = new \ReflectionClass($class);
$params = array_merge(
$params, array_values($reflection->getConstants())
);
} catch (\ReflectionException $exception) {
$this->logger->logException(
$exception, ["app" => $this->appName]
);
}
}
return $params;
}
/**
* Store properties in the cache memory.
*/
private function store()
{
$this->cache->set(self::CACHE_KEY, $this->data);
}
/**
* Get properties array.
*
* @return array The properties array.
*/
public function getArray()
{
return $this->data;
}
/**
* @inheritdoc
*/
public function offsetExists($offset)
{
return isset($this->data[$offset]);
}
/**
* @inheritdoc
*/
public function offsetGet($offset)
{
if (isset($this->data[$offset])) {
return $this->data[$offset];
} else {
return null;
}
}
/**
* @inheritdoc
*/
public function offsetSet($offset, $value)
{
$this->config->setAppValue($this->appName, $offset, $value);
if ($value === App::FALSE_VALUE) {
$value = false;
} elseif ($value === App::TRUE_VALUE) {
$value = true;
}
$this->data[$offset] = $value;
if ($offset === Opt::USE_CACHE && $value === false) {
$this->cache->clear();
} else {
$this->store();
}
}
/**
* @inheritdoc
*/
public function offsetUnset($offset)
{
unset($this->data[$offset]);
}
}

267
lib/Query/DataQuery.php Normal file
View File

@@ -0,0 +1,267 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Query;
use Doctrine\DBAL\Driver\Statement;
use OC\DB\Connection;
use OC\DB\ConnectionFactory;
use OCA\UserSQL\Constant\DB;
use OCA\UserSQL\Constant\Query;
use OCA\UserSQL\Properties;
use OCP\ILogger;
/**
* Used to query a database.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class DataQuery
{
/**
* @var string The application name.
*/
private $appName;
/**
* @var ILogger The logger instance.
*/
private $logger;
/**
* @var Properties The properties array.
*/
private $properties;
/**
* @var QueryProvider The query provider.
*/
private $queryProvider;
/**
* @var Connection The database connection.
*/
private $connection;
/**
* The class constructor.
*
* @param string $AppName The application name.
* @param ILogger $logger The logger instance.
* @param Properties $properties The properties array.
* @param QueryProvider $queryProvider The query provider.
*/
public function __construct(
$AppName, ILogger $logger, Properties $properties,
QueryProvider $queryProvider
) {
$this->appName = $AppName;
$this->logger = $logger;
$this->properties = $properties;
$this->queryProvider = $queryProvider;
$this->connection = false;
}
/**
* Execute an update query.
*
* @param string $queryName The query name.
* @param array $params The query parameters.
*
* @see Query
* @return bool TRUE on success, FALSE otherwise.
*/
public function update($queryName, $params = [])
{
return $this->execQuery($queryName, $params) !== false;
}
/**
* Run a given query and return the result.
*
* @param string $queryName The query to execute.
* @param array $params The query parameters to bind.
* @param int $limit Results limit. Defaults to -1 (no limit).
* @param int $offset Results offset. Defaults to 0.
*
* @return Statement|bool Result of query or FALSE on failure.
*/
private function execQuery(
$queryName, $params = [], $limit = -1, $offset = 0
) {
if ($this->connection === false) {
$this->connectToDatabase();
}
$query = $this->queryProvider[$queryName];
$result = $this->connection->prepare($query, $limit, $offset);
foreach ($params as $param => $value) {
$result->bindValue(":" . $param, $value);
}
$this->logger->debug(
"Executing query:" . $query . ", " . implode(",", $params),
["app" => $this->appName]
);
if ($result->execute() !== true) {
$error = $result->errorInfo();
$this->logger->error(
"Could not execute the query: " . implode(", ", $error),
["app" => $this->appName]
);
return false;
}
return $result;
}
/**
* Connect to the database using Nextcloud's DBAL.
*/
private function connectToDatabase()
{
$connectionFactory = new ConnectionFactory(
\OC::$server->getSystemConfig()
);
$parameters = array(
"host" => $this->properties[DB::HOSTNAME],
"password" => $this->properties[DB::PASSWORD],
"user" => $this->properties[DB::USERNAME],
"dbname" => $this->properties[DB::DATABASE],
"tablePrefix" => ""
);
$this->connection = $connectionFactory->getConnection(
$this->properties[DB::DRIVER], $parameters
);
$this->logger->debug(
"Database connection established.", ["app" => $this->appName]
);
}
/**
* Fetch a value from the first row and the first column which
* the given query returns. Empty result set is consider to be a failure.
*
* @param string $queryName The query to execute.
* @param array $params The query parameters to bind.
* @param bool $failure Value returned on database query failure.
* Defaults to FALSE.
*
* @return array|bool Queried value or $failure value on failure.
*/
public function queryValue($queryName, $params = [], $failure = false)
{
$result = $this->execQuery($queryName, $params);
if ($result === false) {
return false;
}
$row = $result->fetch(\PDO::FETCH_COLUMN);
if ($row === false) {
return $failure;
}
return $row;
}
/**
* Fetch values from the first column which the given query returns.
*
* @param string $queryName The query to execute.
* @param array $params The query parameters to bind.
* @param int $limit Results limit. Defaults to -1 (no limit).
* @param int $offset Results offset. Defaults to 0.
*
* @return array|bool Queried column or FALSE on failure.
*/
public function queryColumn(
$queryName, $params = [], $limit = -1, $offset = 0
) {
$result = $this->execQuery($queryName, $params, $limit, $offset);
if ($result === false) {
return false;
}
$column = $result->fetchAll(\PDO::FETCH_COLUMN);
return $column;
}
/**
* Fetch entity returned by the given query.
*
* @param string $queryName The query to execute.
* @param string $entityClass The entity class name.
* @param array $params The query parameters to bind.
*
* @return mixed|null The queried entity, NULL if it does not exists or
* FALSE on failure.
*/
public function queryEntity($queryName, $entityClass, $params = [])
{
$result = $this->execQuery($queryName, $params);
if ($result === false) {
return false;
}
$result->setFetchMode(\PDO::FETCH_CLASS, $entityClass);
$entity = $result->fetch();
if ($entity === false) {
return null;
}
if (empty($entity) === true) {
$this->logger->debug(
"Empty result for query: " . $queryName,
["app" => $this->appName]
);
return null;
}
return $entity;
}
/**
* Fetch entities returned by the given query.
*
* @param string $queryName The query to execute.
* @param string $entityClass The entity class name.
* @param array $params The query parameters to bind.
* @param int $limit Results limit. Defaults to -1 (no limit).
* @param int $offset Results offset. Defaults to 0.
*
* @return mixed|null The queried entities or FALSE on failure.
*/
public function queryEntities(
$queryName, $entityClass, $params = [], $limit = -1, $offset = 0
) {
$result = $this->execQuery($queryName, $params, $limit, $offset);
if ($result === false) {
return false;
}
$result->setFetchMode(\PDO::FETCH_CLASS, $entityClass);
$entities = $result->fetchAll();
return $entities;
}
}

195
lib/Query/QueryProvider.php Normal file
View File

@@ -0,0 +1,195 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Query;
use OCA\UserSQL\Constant\DB;
use OCA\UserSQL\Constant\Query;
use OCA\UserSQL\Properties;
/**
* Provides queries array.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class QueryProvider implements \ArrayAccess
{
/**
* @var Properties The properties array.
*/
private $properties;
/**
* @var array The queries array.
*/
private $queries;
/**
* The class constructor.
*
* @param Properties $properties The properties array.
*/
public function __construct(Properties $properties)
{
$this->properties = $properties;
$this->loadQueries();
}
/**
* Load queries to the array.
*/
private function loadQueries()
{
$group = $this->properties[DB::GROUP_TABLE];
$userGroup = $this->properties[DB::USER_GROUP_TABLE];
$user = $this->properties[DB::USER_TABLE];
$gAdmin = $this->properties[DB::GROUP_ADMIN_COLUMN];
$gGID = $this->properties[DB::GROUP_GID_COLUMN];
$gName = $this->properties[DB::GROUP_NAME_COLUMN];
$uAvatar = $this->properties[DB::USER_AVATAR_COLUMN];
$uEmail = $this->properties[DB::USER_EMAIL_COLUMN];
$uHome = $this->properties[DB::USER_HOME_COLUMN];
$uName = $this->properties[DB::USER_NAME_COLUMN];
$uPassword = $this->properties[DB::USER_PASSWORD_COLUMN];
$uUID = $this->properties[DB::USER_UID_COLUMN];
$ugGID = $this->properties[DB::USER_GROUP_GID_COLUMN];
$ugUID = $this->properties[DB::USER_GROUP_UID_COLUMN];
$gidParam = Query::GID_PARAM;
$nameParam = Query::NAME_PARAM;
$passwordParam = Query::PASSWORD_PARAM;
$searchParam = Query::SEARCH_PARAM;
$uidParam = Query::UID_PARAM;
$groupColumns
= "$gGID AS gid, " .
"$gName AS name, " .
"$gAdmin AS admin";
$userColumns
= "$uUID AS uid, " .
"$uName AS name, " .
"$uEmail AS email, " .
"$uHome AS home, " .
"$uAvatar AS avatar";
$this->queries = [
Query::BELONGS_TO_ADMIN =>
"SELECT COUNT($gGID) > 0 AS admin " .
"FROM $group, $userGroup " .
"WHERE $ugGID = $gGID " .
"AND $ugUID = :$uidParam " .
"AND $gAdmin",
Query::COUNT_GROUPS =>
"SELECT COUNT($ugGID) " .
"FROM $userGroup " .
"WHERE $ugGID = :$gidParam " .
"AND $ugUID " .
"LIKE :$searchParam",
Query::COUNT_USERS =>
"SELECT COUNT($uUID) AS count " .
"FROM $user " .
"WHERE $uUID LIKE :$searchParam",
Query::FIND_GROUP =>
"SELECT $groupColumns " .
"FROM $group " .
"WHERE $gGID = :$gidParam",
Query::FIND_GROUP_USERS =>
"SELECT $ugUID AS uid " .
"FROM $userGroup " .
"WHERE $ugGID = :$gidParam " .
"AND $ugUID " .
"LIKE :$searchParam " .
"ORDER BY $ugUID",
Query::FIND_GROUPS =>
"SELECT $groupColumns " .
"FROM $group " .
"WHERE $gGID LIKE :$searchParam " .
"ORDER BY $gGID",
Query::FIND_USER =>
"SELECT $userColumns, $uPassword AS password " .
"FROM $user " .
"WHERE $uUID = :$uidParam",
Query::FIND_USER_GROUPS =>
"SELECT $groupColumns " .
"FROM $group, $userGroup " .
"WHERE $ugGID = $gGID " .
"AND $ugUID = :$uidParam " .
"ORDER BY $gGID",
Query::FIND_USERS =>
"SELECT $userColumns " .
"FROM $user " .
"WHERE $uUID LIKE :$searchParam " .
"ORDER BY $uUID",
Query::SAVE_USER =>
"UPDATE $user " .
"SET $uPassword = :$passwordParam, " .
"$uName = :$nameParam " .
"WHERE $uUID = :$uidParam",
];
}
/**
* @inheritdoc
*/
public function offsetExists($offset)
{
return isset($this->queries[$offset]);
}
/**
* @inheritdoc
*/
public function offsetGet($offset)
{
if (isset($this->queries[$offset])) {
return $this->queries[$offset];
} else {
return null;
}
}
/**
* @inheritdoc
*/
public function offsetSet($offset, $value)
{
$this->queries[$offset] = $value;
}
/**
* @inheritdoc
*/
public function offsetUnset($offset)
{
unset($this->queries[$offset]);
}
}

View File

@@ -0,0 +1,150 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Repository;
use OCA\UserSQL\Constant\Query;
use OCA\UserSQL\Model\Group;
use OCA\UserSQL\Query\DataQuery;
/**
* The group repository.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class GroupRepository
{
/**
* @var DataQuery The data query object.
*/
private $dataQuery;
/**
* The class constructor.
*
* @param DataQuery $dataQuery The data query object.
*/
public function __construct(DataQuery $dataQuery)
{
$this->dataQuery = $dataQuery;
}
/**
* Get a group entity object.
*
* @param string $gid The group ID.
*
* @return Group The group entity, NULL if it does not exists or
* FALSE on failure.
*/
public function findByGid($gid)
{
return $this->dataQuery->queryEntity(
Query::FIND_GROUP, Group::class, [Query::GID_PARAM => $gid]
);
}
/**
* Get all groups a user belongs to.
*
* @param string $uid The user ID.
*
* @return Group[] Array of group entity objects or FALSE on failure.
*/
public function findAllByUid($uid)
{
return $this->dataQuery->queryEntities(
Query::FIND_USER_GROUPS, Group::class, [Query::UID_PARAM => $uid]
);
}
/**
* Get a list of all user IDs belonging to the group.
*
* @param string $gid The group ID.
* @param string $search The UID search term. Defaults to "" (empty string).
* @param int $limit (optional) Results limit.
* Defaults to -1 (no limit).
* @param int $offset (optional) Results offset. Defaults to 0.
*
* @return string[] Array of UIDs belonging to the group
* or FALSE on failure.
*/
public function findAllUidsBySearchTerm(
$gid, $search = "", $limit = -1, $offset = 0
) {
return $this->dataQuery->queryColumn(
Query::FIND_GROUP_USERS,
[Query::GID_PARAM => $gid, Query::SEARCH_PARAM => $search], $limit,
$offset
);
}
/**
* Get an array of group entity objects.
*
* @param string $search The search term. Defaults to "" (empty string).
* @param int $limit (optional) Results limit.
* Defaults to -1 (no limit).
* @param int $offset (optional) Results offset. Defaults to 0.
*
* @return Group[] Array of group entity objects or FALSE on failure.
*/
public function findAllBySearchTerm($search = "", $limit = -1, $offset = 0)
{
return $this->dataQuery->queryEntities(
Query::FIND_GROUPS, Group::class, [Query::SEARCH_PARAM => $search],
$limit, $offset
);
}
/**
* Get the number of users in given group matching the search term.
*
* @param string $gid The group ID.
* @param string $search The UID search term. Defaults to "" (empty string).
*
* @return int The number of users in given group matching the search term
* or FALSE on failure.
*/
public function countAll($gid, $search = "")
{
return $this->dataQuery->queryValue(
Query::COUNT_GROUPS,
[Query::GID_PARAM => $gid, Query::SEARCH_PARAM => $search]
);
}
/**
* Find out if the user belongs to any admin group.
*
* @param string $uid The user ID.
*
* @return bool|null TRUE if the user belongs to any admin group,
* FALSE if not, NULL on failure.
*/
public function belongsToAdmin($uid)
{
return $this->dataQuery->queryValue(
Query::BELONGS_TO_ADMIN, [Query::UID_PARAM => $uid], null
);
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* Nextcloud - user_sql
*
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\UserSQL\Repository;
use OCA\UserSQL\Constant\Query;
use OCA\UserSQL\Model\User;
use OCA\UserSQL\Query\DataQuery;
/**
* The user repository.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class UserRepository
{
/**
* @var DataQuery The data query object.
*/
private $dataQuery;
/**
* The class constructor.
*
* @param DataQuery $dataQuery The data query object.
*/
public function __construct(DataQuery $dataQuery)
{
$this->dataQuery = $dataQuery;
}
/**
* Get a user entity object.
*
* @param string $uid The user ID.
*
* @return User The user entity, NULL if it does not exists or
* FALSE on failure.
*/
public function findByUid($uid)
{
return $this->dataQuery->queryEntity(
Query::FIND_USER, User::class, [Query::UID_PARAM => $uid]
);
}
/**
* Get an array of user entity objects.
*
* @param string $search The search term. Defaults to "" (empty string).
* @param int $limit (optional) Results limit.
* Defaults to -1 (no limit).
* @param int $offset (optional) Results offset. Defaults to 0.
*
* @return User[] Array of user entity objects or FALSE on failure.
*/
public function findAllBySearchTerm($search = "", $limit = -1, $offset = 0)
{
return $this->dataQuery->queryEntities(
Query::FIND_USERS, User::class, [Query::SEARCH_PARAM => $search],
$limit, $offset
);
}
/**
* Get the number of users.
*
* @param string $search The search term. Defaults to "" (empty string).
*
* @return int The number of users or FALSE on failure.
*/
public function countAll($search = "")
{
return $this->dataQuery->queryValue(
Query::COUNT_USERS, [Query::SEARCH_PARAM => $search]
);
}
/**
* Save an user entity object.
*
* @param User $user The user entity.
*
* @return bool TRUE on success, FALSE otherwise.
*/
public function save($user)
{
return $this->dataQuery->update(
Query::SAVE_USER, [
Query::NAME_PARAM => $user->name,
Query::PASSWORD_PARAM => $user->password,
Query::UID_PARAM => $user->uid
]
);
}
}

View File

@@ -1,10 +1,9 @@
<?php
/**
* @copyright Copyright (c)
* Nextcloud - user_sql
*
* @author
*
* @license GNU AGPL version 3 or any later version
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,87 +16,64 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\user_sql\Settings;
namespace OCA\UserSQL\Settings;
use OCA\UserSQL\Properties;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Defaults;
use OCP\IConfig;
use OCP\IL10N;
use OCP\Settings\ISettings;
/**
* The administrator's settings page.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Admin implements ISettings
{
/** @var IL10N */
private $l10n;
/** @var Defaults */
private $defaults;
/** @var IConfig */
private $config;
/**
* @var string The application name.
*/
private $appName;
/**
* @var Properties The properties array.
*/
private $properties;
/**
* @param IL10N $l10n
* @param Defaults $defaults
* @param IConfig $config
* The class constructor,
*
* @param string $AppName The application name.
* @param Properties $properties The properties array.
*/
public function __construct(
IL10N $l10n,
Defaults $defaults,
IConfig $config
) {
$this->l10n = $l10n;
$this->defaults = $defaults;
$this->config = $config;
$this->helper = new \OCA\user_sql\lib\Helper();
$this->params = $this->helper->getParameterArray();
$this->settings = $this->helper->loadSettingsForDomain('default');
public function __construct($AppName, Properties $properties)
{
$this->appName = $AppName;
$this->properties = $properties;
}
/**
* @return TemplateResponse
* @inheritdoc
*/
public function getForm()
{
$type = $this->config->getAppValue('user_sql', 'type');
$trusted_domains = \OC::$server->getConfig()->getSystemValue('trusted_domains');
$inserted = array('default');
array_splice($trusted_domains, 0, 0, $inserted);
$params = [
'type' => $type,
];
$params['allowed_domains'] = array_unique($trusted_domains);
foreach ($this->params as $key) {
$value = $this->settings[$key];
$params[$key] = $value;
}
$params["config"] = $this->config;
return new TemplateResponse('user_sql', 'admin', $params);
return new TemplateResponse($this->appName, "admin", $this->properties->getArray());
}
/**
* @return string the section ID, e.g. 'sharing'
* @inheritdoc
*/
public function getSection()
{
return 'user_sql';
return $this->appName;
}
/**
* @return int whether the form should be rather on the top or bottom of
* the admin section. The forms are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
*
* keep the server setting at the top, right after "server settings"
* @inheritdoc
*/
public function getPriority()
{
return 0;
return 25;
}
}

View File

@@ -1,10 +1,9 @@
<?php
/**
* @copyright Copyright (c)
* Nextcloud - user_sql
*
* @author
*
* @license GNU AGPL version 3 or any later version
* @copyright 2018 Marcin Łojewski <dev@mlojewski.me>
* @author Marcin Łojewski <dev@mlojewski.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,59 +16,79 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace OCA\user_sql\Settings;
namespace OCA\UserSQL\Settings;
use OCP\IL10N;
use OCP\Settings\IIconSection;
use OCP\IURLGenerator;
use OCP\Settings\IIconSection;
/**
* The section item.
*
* @author Marcin Łojewski <dev@mlojewski.me>
*/
class Section implements IIconSection
{
/** @var IL10N */
private $l;
/**
* @var string The application name.
*/
private $appName;
/**
* @var IURLGenerator The URL generator.
*/
private $urlGenerator;
/**
* @var IL10N The localization service.
*/
private $localization;
/**
* @param IL10N $l
* The class constructor.
*
* @param string $AppName The application name.
* @param IURLGenerator $urlGenerator The URL generator.
* @param IL10N $localization The localization service.
*/
public function __construct(IURLGenerator $url, IL10N $l)
{
$this->l = $l;
$this->url = $url;
public function __construct(
$AppName, IURLGenerator $urlGenerator, IL10N $localization
) {
$this->appName = $AppName;
$this->urlGenerator = $urlGenerator;
$this->localization = $localization;
}
/**
* {@inheritdoc}
* @inheritdoc
*/
public function getID()
{
return 'user_sql';
return $this->appName;
}
/**
* {@inheritdoc}
* @inheritdoc
*/
public function getName()
{
return $this->l->t('User SQL');
return $this->localization->t("SQL Backends");
}
/**
* {@inheritdoc}
* @inheritdoc
*/
public function getPriority()
{
return 75;
return 25;
}
/**
* {@inheritdoc}
* @inheritdoc
*/
public function getIcon()
{
return $this->url->imagePath('user_sql', 'app-dark.svg');
return $this->urlGenerator->imagePath($this->appName, "app-dark.svg");
}
}

View File

@@ -1,330 +0,0 @@
<?php
/**
* @file
* Secure password hashing functions for user authentication.
* Adopted from Drupal 7.x WD 2018-01-04
*
* Based on the Portable PHP password hashing framework.
* @see http://www.openwall.com/phpass/
*
* An alternative or custom version of this password hashing API may be
* used by setting the variable password_inc to the name of the PHP file
* containing replacement user_hash_password(), user_check_password(), and
* user_needs_new_hash() functions.
*/
/**
* The standard log2 number of iterations for password stretching. This should
* increase by 1 every Drupal version in order to counteract increases in the
* speed and power of computers available to crack the hashes.
*/
define('HASH_COUNT', 15);
/**
* The minimum allowed log2 number of iterations for password stretching.
*/
define('MIN_HASH_COUNT', 7);
/**
* The maximum allowed log2 number of iterations for password stretching.
*/
define('MAX_HASH_COUNT', 30);
/**
* The expected (and maximum) number of characters in a hashed password.
*/
define('HASH_LENGTH', 55);
/**
* Returns a string for mapping an int to the corresponding base 64 character.
*/
function _password_itoa64()
{
return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
}
/**
* Encodes bytes into printable base 64 using the *nix standard from crypt().
*
* @param $input
* The string containing bytes to encode.
* @param $count
* The number of characters (bytes) to encode.
*
* @return
* Encoded string
*/
function _password_base64_encode($input, $count)
{
$output = '';
$i = 0;
$itoa64 = _password_itoa64();
do {
$value = ord($input[$i++]);
$output .= $itoa64[$value & 0x3f];
if ($i < $count) {
$value |= ord($input[$i]) << 8;
}
$output .= $itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count) {
break;
}
if ($i < $count) {
$value |= ord($input[$i]) << 16;
}
$output .= $itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count) {
break;
}
$output .= $itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
/**
* Returns a string of highly randomized bytes (over the full 8-bit range).
*
* This function is better than simply calling mt_rand() or any other built-in
* PHP function because it can return a long string of bytes (compared to < 4
* bytes normally from mt_rand()) and uses the best available pseudo-random
* source.
*
* @param $count
* The number of characters (bytes) to return in the string.
*/
function _random_bytes($count)
{
// $random_state does not use static as it stores random bytes.
static $random_state, $bytes, $has_openssl;
$missing_bytes = $count - strlen($bytes);
if ($missing_bytes > 0) {
// PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes()
// locking on Windows and rendered it unusable.
if (!isset($has_openssl)) {
$has_openssl = version_compare(PHP_VERSION, '5.3.4',
'>=') && function_exists('openssl_random_pseudo_bytes');
}
// openssl_random_pseudo_bytes() will find entropy in a system-dependent
// way.
if ($has_openssl) {
$bytes .= openssl_random_pseudo_bytes($missing_bytes);
}
// Else, read directly from /dev/urandom, which is available on many *nix
// systems and is considered cryptographically secure.
elseif ($fh = @fopen('/dev/urandom', 'rb')) {
// PHP only performs buffered reads, so in reality it will always read
// at least 4096 bytes. Thus, it costs nothing extra to read and store
// that much so as to speed any additional invocations.
$bytes .= fread($fh, max(4096, $missing_bytes));
fclose($fh);
}
// If we couldn't get enough entropy, this simple hash-based PRNG will
// generate a good set of pseudo-random bytes on any system.
// Note that it may be important that our $random_state is passed
// through hash() prior to being rolled into $output, that the two hash()
// invocations are different, and that the extra input into the first one -
// the microtime() - is prepended rather than appended. This is to avoid
// directly leaking $random_state via the $output stream, which could
// allow for trivial prediction of further "random" numbers.
if (strlen($bytes) < $count) {
// Initialize on the first call. The contents of $_SERVER includes a mix of
// user-specific and system information that varies a little with each page.
if (!isset($random_state)) {
$random_state = print_r($_SERVER, true);
if (function_exists('getmypid')) {
// Further initialize with the somewhat random PHP process ID.
$random_state .= getmypid();
}
$bytes = '';
}
do {
$random_state = hash('sha256', microtime() . mt_rand() . $random_state);
$bytes .= hash('sha256', mt_rand() . $random_state, true);
} while (strlen($bytes) < $count);
}
}
$output = substr($bytes, 0, $count);
$bytes = substr($bytes, $count);
return $output;
}
/**
* Generates a random base 64-encoded salt prefixed with settings for the hash.
*
* Proper use of salts may defeat a number of attacks, including:
* - The ability to try candidate passwords against multiple hashes at once.
* - The ability to use pre-hashed lists of candidate passwords.
* - The ability to determine whether two users have the same (or different)
* password without actually having to guess one of the passwords.
*
* @param $count_log2
* Integer that determines the number of iterations used in the hashing
* process. A larger value is more secure, but takes more time to complete.
*
* @return
* A 12 character string containing the iteration count and a random salt.
*/
function _password_generate_salt($count_log2)
{
$output = '$S$';
// Ensure that $count_log2 is within set bounds.
$count_log2 = _password_enforce_log2_boundaries($count_log2);
// We encode the final log2 iteration count in base 64.
$itoa64 = _password_itoa64();
$output .= $itoa64[$count_log2];
// 6 bytes is the standard salt for a portable phpass hash.
$output .= _password_base64_encode(_random_bytes(6), 6);
return $output;
}
/**
* Ensures that $count_log2 is within set bounds.
*
* @param $count_log2
* Integer that determines the number of iterations used in the hashing
* process. A larger value is more secure, but takes more time to complete.
*
* @return
* Integer within set bounds that is closest to $count_log2.
*/
function _password_enforce_log2_boundaries($count_log2)
{
if ($count_log2 < MIN_HASH_COUNT) {
return MIN_HASH_COUNT;
} elseif ($count_log2 > MAX_HASH_COUNT) {
return MAX_HASH_COUNT;
}
return (int)$count_log2;
}
/**
* Hash a password using a secure stretched hash.
*
* By using a salt and repeated hashing the password is "stretched". Its
* security is increased because it becomes much more computationally costly
* for an attacker to try to break the hash by brute-force computation of the
* hashes of a large number of plain-text words or strings to find a match.
*
* @param $algo
* The string name of a hashing algorithm usable by hash(), like 'sha256'.
* @param $password
* Plain-text password up to 512 bytes (128 to 512 UTF-8 characters) to hash.
* @param $setting
* An existing hash or the output of _password_generate_salt(). Must be
* at least 12 characters (the settings and salt).
*
* @return
* A string containing the hashed password (and salt) or FALSE on failure.
* The return string will be truncated at DRUPAL_HASH_LENGTH characters max.
*/
function _password_crypt($algo, $password, $setting)
{
// Prevent DoS attacks by refusing to hash large passwords.
if (strlen($password) > 512) {
return false;
}
// The first 12 characters of an existing hash are its setting string.
$setting = substr($setting, 0, 12);
if ($setting[0] != '$' || $setting[2] != '$') {
return false;
}
$count_log2 = _password_get_count_log2($setting);
// Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
if ($count_log2 < MIN_HASH_COUNT || $count_log2 > MAX_HASH_COUNT) {
return false;
}
$salt = substr($setting, 4, 8);
// Hashes must have an 8 character salt.
if (strlen($salt) != 8) {
return false;
}
// Convert the base 2 logarithm into an integer.
$count = 1 << $count_log2;
// We rely on the hash() function being available in PHP 5.2+.
$hash = hash($algo, $salt . $password, true);
do {
$hash = hash($algo, $hash . $password, true);
} while (--$count);
$len = strlen($hash);
$output = $setting . _password_base64_encode($hash, $len);
// _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
// _password_base64_encode() of a 64 byte sha512 will always be 86 characters.
$expected = 12 + ceil((8 * $len) / 6);
return (strlen($output) == $expected) ? substr($output, 0, HASH_LENGTH) : false;
}
/**
* Parse the log2 iteration count from a stored hash or setting string.
*/
function _password_get_count_log2($setting)
{
$itoa64 = _password_itoa64();
return strpos($itoa64, $setting[3]);
}
/**
* Hash a password using a secure hash.
*
* @param $password
* A plain-text password.
* @param $count_log2
* Optional integer to specify the iteration count. Generally used only during
* mass operations where a value less than the default is needed for speed.
*
* @return
* A string containing the hashed password (and a salt), or FALSE on failure.
*/
function user_hash_password($password, $count_log2 = 0)
{
if (empty($count_log2)) {
// Use the standard iteration count.
$count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT);
}
return _password_crypt('sha512', $password, _password_generate_salt($count_log2));
}
/**
* Check whether a plain text password matches a stored hashed password.
*
* @param $password
* A plain-text password
* @param $hashpass
*
* @return
* TRUE or FALSE.
*/
function user_check_password($password, $hashpass)
{
$stored_hash = $hashpass;
$type = substr($stored_hash, 0, 3);
switch ($type) {
case '$S$':
// A normal Drupal 7 password using sha512.
$hash = _password_crypt('sha512', $password, $stored_hash);
break;
case '$H$':
// phpBB3 uses "$H$" for the same thing as "$P$".
case '$P$':
// A phpass password generated using md5. This is an
// imported password or from an earlier Drupal version.
$hash = _password_crypt('md5', $password, $stored_hash);
break;
default:
return false;
}
return ($hash && $stored_hash == $hash);
}

View File

@@ -1,88 +0,0 @@
<?php
namespace OCA\user_sql;
use OCP\Util;
class OC_GROUP_SQL extends \OC_Group_Backend implements \OCP\GroupInterface
{
protected $settings;
protected $helper;
public function __construct()
{
$this->helper = new \OCA\user_sql\lib\Helper();
$domain = \OC::$server->getRequest()->getServerHost();
$this->settings = $this->helper->loadSettingsForDomain($domain);
$this->helper->connectToDb($this->settings);
return false;
}
public function getUserGroups($uid)
{
if (empty($this->settings['sql_group_table'])) {
Util::writeLog('OC_USER_SQL', "Group table not configured", Util::DEBUG);
return [];
}
$rows = $this->helper->runQuery('getUserGroups', array('uid' => $uid), false, true);
if ($rows === false) {
Util::writeLog('OC_USER_SQL', "Found no group", Util::DEBUG);
return [];
}
$groups = array();
foreach ($rows as $row) {
$groups[] = $row[$this->settings['col_group_name']];
}
return $groups;
}
public function getGroups($search = '', $limit = null, $offset = null)
{
if (empty($this->settings['sql_group_table'])) {
return [];
}
$search = "%" . $search . "%";
$rows = $this->helper->runQuery('getGroups', array('search' => $search), false, true,
array('limit' => $limit, 'offset' => $offset));
if ($rows === false) {
return [];
}
$groups = array();
foreach ($rows as $row) {
$groups[] = $row[$this->settings['col_group_name']];
}
return $groups;
}
public function usersInGroup($gid, $search = '', $limit = null, $offset = null)
{
if (empty($this->settings['sql_group_table'])) {
Util::writeLog('OC_USER_SQL', "Group table not configured", Util::DEBUG);
return [];
}
$rows = $this->helper->runQuery('getGroupUsers', array('gid' => $gid), false, true);
if ($rows === false) {
Util::writeLog('OC_USER_SQL', "Found no users for group", Util::DEBUG);
return [];
}
$users = array();
foreach ($rows as $row) {
$users[] = $row[$this->settings['col_group_username']];
}
return $users;
}
public function countUsersInGroup($gid, $search = '')
{
if (empty($this->settings['sql_group_table'])) {
return 0;
}
$search = "%" . $search . "%";
$count = $this->helper->runQuery('countUsersInGroup', array('gid' => $gid, 'search' => $search));
if ($count === false) {
return 0;
} else {
return intval(reset($count));
}
}
}

View File

@@ -1,427 +0,0 @@
<?php
/**
* Nextcloud - user_sql
*
* @author Andreas Böhler and contributors
* @copyright 2012-2015 Andreas Böhler <dev (at) aboehler (dot) at>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\user_sql\lib;
use OCP\IConfig;
use OCP\Util;
class Helper
{
protected $db;
protected $db_conn;
protected $settings;
/**
* The default constructor initializes some parameters
*/
public function __construct()
{
$this->db_conn = false;
}
/**
* Return an array with all supported parameters
* @return array Containing strings of the parameters
*/
public function getParameterArray()
{
$params = array(
'sql_hostname',
'sql_username',
'sql_password',
'sql_database',
'sql_table',
'sql_driver',
'col_username',
'col_password',
'col_active',
'col_displayname',
'col_email',
'col_gethome',
'set_active_invert',
'set_allow_pwchange',
'set_default_domain',
'set_strip_domain',
'set_crypt_type',
'set_mail_sync_mode',
'set_enable_gethome',
'set_gethome_mode',
'set_gethome',
'sql_group_table',
'col_group_username',
'col_group_name'
);
return $params;
}
/**
* Load the settings for a given domain. If the domain is not found,
* the settings for 'default' are returned instead.
* @param string $domain The domain name
* @return array of settings
*/
public function loadSettingsForDomain($domain)
{
Util::writeLog('OC_USER_SQL', "Trying to load settings for domain: " . $domain, Util::DEBUG);
$settings = array();
$sql_host = \OC::$server->getConfig()->getAppValue('user_sql', 'sql_hostname_' . $domain, '');
if ($sql_host === '') {
$domain = 'default';
}
$params = $this->getParameterArray();
foreach ($params as $param) {
$settings[$param] = \OC::$server->getConfig()->getAppValue('user_sql', $param . '_' . $domain, '');
}
Util::writeLog('OC_USER_SQL', "Loaded settings for domain: " . $domain, Util::DEBUG);
return $settings;
}
/**
* Run a given query type and return the results
* @param string $type The type of query to run
* @param array $params The parameter array of the query (i.e. the values to bind as key-value pairs)
* @param bool $execOnly Only execute the query, but don't fetch the results (optional, default = false)
* @param bool $fetchArray Fetch an array instead of a single row (optional, default=false)
* @param array $limits use the given limits for the query (optional, default = empty)
* @return mixed
*/
public function runQuery($type, $params, $execOnly = false, $fetchArray = false, $limits = array())
{
Util::writeLog('OC_USER_SQL', "Entering runQuery for type: " . $type, Util::DEBUG);
if (!$this->db_conn) {
return false;
}
switch ($type) {
case 'getHome':
$query = "SELECT " . $this->settings['col_gethome'] . " FROM " . $this->settings['sql_table'] . " WHERE " . $this->settings['col_username'] . " = :uid";
break;
case 'getMail':
$query = "SELECT " . $this->settings['col_email'] . " FROM " . $this->settings['sql_table'] . " WHERE " . $this->settings['col_username'] . " = :uid";
break;
case 'setMail':
$query = "UPDATE " . $this->settings['sql_table'] . " SET " . $this->settings['col_email'] . " = :currMail WHERE " . $this->settings['col_username'] . " = :uid";
break;
case 'getPass':
$query = "SELECT " . $this->settings['col_password'] . " FROM " . $this->settings['sql_table'] . " WHERE " . $this->settings['col_username'] . " = :uid";
if ($this->settings['col_active'] !== '') {
$query .= " AND " . ($this->settings['set_active_invert'] === 'true' ? "NOT " : "") . $this->settings['col_active'];
}
break;
case 'setPass':
$query = "UPDATE " . $this->settings['sql_table'] . " SET " . $this->settings['col_password'] . " = :enc_password WHERE " . $this->settings['col_username'] . " = :uid";
break;
case 'getRedmineSalt':
$query = "SELECT salt FROM " . $this->settings['sql_table'] . " WHERE " . $this->settings['col_username'] . " = :uid;";
break;
case 'countUsers':
$query = "SELECT COUNT(*) FROM " . $this->settings['sql_table'] . " WHERE " . $this->settings['col_username'] . " LIKE :search";
if ($this->settings['col_active'] !== '') {
$query .= " AND " . ($this->settings['set_active_invert'] === 'true' ? "NOT " : "") . $this->settings['col_active'];
}
break;
case 'getUsers':
$query = "SELECT " . $this->settings['col_username'] . " FROM " . $this->settings['sql_table'];
$query .= " WHERE " . $this->settings['col_username'] . " LIKE :search";
if ($this->settings['col_active'] !== '') {
$query .= " AND " . ($this->settings['set_active_invert'] === 'true' ? "NOT " : "") . $this->settings['col_active'];
}
$query .= " ORDER BY " . $this->settings['col_username'];
break;
case 'userExists':
$query = "SELECT " . $this->settings['col_username'] . " FROM " . $this->settings['sql_table'] . " WHERE " . $this->settings['col_username'] . " = :uid";
if ($this->settings['col_active'] !== '') {
$query .= " AND " . ($this->settings['set_active_invert'] === 'true' ? "NOT " : "") . $this->settings['col_active'];
}
break;
case 'getDisplayName':
$query = "SELECT " . $this->settings['col_displayname'] . " FROM " . $this->settings['sql_table'] . " WHERE " . $this->settings['col_username'] . " = :uid";
if ($this->settings['col_active'] !== '') {
$query .= " AND " . ($this->settings['set_active_invert'] === 'true' ? "NOT " : "") . $this->settings['col_active'];
}
break;
case 'mysqlEncryptSalt':
$query = "SELECT ENCRYPT(:pw, :salt);";
break;
case 'mysqlEncrypt':
$query = "SELECT ENCRYPT(:pw);";
break;
case 'mysqlPassword':
$query = "SELECT PASSWORD(:pw);";
break;
case 'getUserGroups':
$query = "SELECT " . $this->settings['col_group_name'] . " FROM " . $this->settings['sql_group_table'] . " WHERE " . $this->settings['col_group_username'] . " = :uid";
break;
case 'getGroups':
$query = "SELECT distinct " . $this->settings['col_group_name'] . " FROM " . $this->settings['sql_group_table'] . " WHERE " . $this->settings['col_group_name'] . " LIKE :search";
break;
case 'getGroupUsers':
$query = "SELECT distinct " . $this->settings['col_group_username'] . " FROM " . $this->settings['sql_group_table'] . " WHERE " . $this->settings['col_group_name'] . " = :gid";
break;
case 'countUsersInGroup':
$query = "SELECT count(" . $this->settings['col_group_username'] . ") FROM " . $this->settings['sql_group_table'] . " WHERE " . $this->settings['col_group_name'] . " = :gid AND " . $this->settings['col_group_username'] . " LIKE :search";
break;
}
if (isset($limits['limit']) && $limits['limit'] !== null) {
$limit = intval($limits['limit']);
$query .= " LIMIT " . $limit;
}
if (isset($limits['offset']) && $limits['offset'] !== null) {
$offset = intval($limits['offset']);
$query .= " OFFSET " . $offset;
}
Util::writeLog('OC_USER_SQL', "Preparing query: $query", Util::DEBUG);
$result = $this->db->prepare($query);
foreach ($params as $param => $value) {
$result->bindValue(":" . $param, $value);
}
Util::writeLog('OC_USER_SQL', "Executing query...", Util::DEBUG);
if (!$result->execute()) {
$err = $result->errorInfo();
Util::writeLog('OC_USER_SQL', "Query failed: " . $err[2], Util::DEBUG);
return false;
}
if ($execOnly === true) {
return true;
}
Util::writeLog('OC_USER_SQL', "Fetching result...", Util::DEBUG);
if ($fetchArray === true) {
$row = $result->fetchAll();
} else {
$row = $result->fetch();
}
if (!$row) {
return false;
}
return $row;
}
/**
* Connect to the database using Nextcloud's DBAL
* @param array $settings The settings for the connection
* @return bool
*/
public function connectToDb($settings)
{
$this->settings = $settings;
$cm = new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig());
$parameters = array(
'host' => $this->settings['sql_hostname'],
'password' => $this->settings['sql_password'],
'user' => $this->settings['sql_username'],
'dbname' => $this->settings['sql_database'],
'tablePrefix' => ''
);
try {
$this->db = $cm->getConnection($this->settings['sql_driver'], $parameters);
$this->db->query("SET NAMES 'UTF8'");
$this->db_conn = true;
return true;
} catch (\Exception $e) {
Util::writeLog('OC_USER_SQL', 'Failed to connect to the database: ' . $e->getMessage(), Util::ERROR);
$this->db_conn = false;
return false;
}
}
/**
* Check if all of the given columns exist
* @param array $parameters The connection parameters
* @param string $sql_driver The SQL driver to use
* @param string $table The table name to check
* @param array $cols The columns to check
* @param array True if found, otherwise false
* @return bool|string
*/
public function verifyColumns($parameters, $sql_driver, $table, $cols)
{
$columns = $this->getColumns($parameters, $sql_driver, $table);
$res = true;
$err = '';
foreach ($cols as $col) {
if (!in_array($col, $columns, true)) {
$res = false;
$err .= $col . ' ';
}
}
if ($res) {
return true;
} else {
return $err;
}
}
/**
* Check if a given table exists
* @param array $parameters The connection parameters
* @param string $sql_driver The SQL driver to use
* @param string $table The table name to check
* @param array True if found, otherwise false
* @return bool
*/
public function verifyTable($parameters, $sql_driver, $table)
{
$tablesWithSchema = $this->getTables($parameters, $sql_driver, true);
$tablesWithoutSchema = $this->getTables($parameters, $sql_driver, false);
return in_array($table, $tablesWithSchema, true) || in_array($table, $tablesWithoutSchema, true);
}
/**
* Retrieve a list of tables for the given connection parameters
* @param array $parameters The connection parameters
* @param string $sql_driver The SQL driver to use
* @param boolean $schema Return table name with schema
* @return array The found tables, empty if an error occurred
*/
public function getTables($parameters, $sql_driver, $schema = true)
{
$cm = new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig());
try {
$conn = $cm->getConnection($sql_driver, $parameters);
$platform = $conn->getDatabasePlatform();
$queryTables = $platform->getListTablesSQL();
$queryViews = $platform->getListViewsSQL($parameters['dbname']);
$ret = array();
$result = $conn->executeQuery($queryTables);
while ($row = $result->fetch()) {
$name = $this->getTableNameFromRow($sql_driver, $parameters['dbname'], $row, $schema);
$ret[] = $name;
}
$result = $conn->executeQuery($queryViews);
while ($row = $result->fetch()) {
$name = $this->getViewNameFromRow($sql_driver, $row, $schema);
$ret[] = $name;
}
return $ret;
} catch (\Exception $e) {
return array();
}
}
/**
* Retrieve table name from database list table SQL
* @param string $sql_driver The SQL driver to use
* @param string $dbname The database name
* @param array $row Query result row
* @param boolean $schema Return table name with schema
* @return string Table name
*/
public function getTableNameFromRow($sql_driver, $dbname, $row, $schema)
{
switch ($sql_driver) {
case 'mysql':
return $row['Tables_in_' . $dbname];
case 'pgsql':
if ($schema) {
return $row['schema_name'] . '.' . $row['table_name'];
} else {
return $row['table_name'];
}
default:
return null;
}
}
/**
* Retrieve view name from database list table SQL
* @param string $sql_driver The SQL driver to use
* @param array $row Query result row
* @param boolean $schema Return table name with schema
* @return string Table name
*/
public function getViewNameFromRow($sql_driver, $row, $schema)
{
switch ($sql_driver) {
case 'mysql':
return $row['TABLE_NAME'];
case 'pgsql':
if ($schema) {
return $row['schemaname'] . '.' . $row['viewname'];
} else {
return $row['viewname'];
}
default:
return null;
}
}
/**
* Retrieve a list of columns for the given connection parameters
* @param array $parameters The connection parameters
* @param string $sql_driver The SQL driver to use
* @param string $table The SQL table to work with
* @return array The found column, empty if an error occured
*/
public function getColumns($parameters, $sql_driver, $table)
{
$cm = new \OC\DB\ConnectionFactory(\OC::$server->getSystemConfig());
try {
$conn = $cm->getConnection($sql_driver, $parameters);
$platform = $conn->getDatabasePlatform();
$query = $platform->getListTableColumnsSQL($table);
$result = $conn->executeQuery($query);
$ret = array();
while ($row = $result->fetch()) {
switch ($sql_driver) {
case 'mysql':
$name = $row['Field'];
break;
case 'pgsql':
$name = $row['field'];
break;
default:
return $ret;
}
$ret[] = $name;
}
return $ret;
} catch (\Exception $e) {
return array();
}
}
}

View File

@@ -1,981 +0,0 @@
<?php
/**
* Nextcloud - user_sql
*
* @author Andreas Böhler and contributors
* @copyright 2012-2015 Andreas Böhler <dev (at) aboehler (dot) at>
*
* credits go to Ed W for several SQL injection fixes and caching support
* credits go to Frédéric France for providing Joomla support
* credits go to Mark Jansenn for providing Joomla 2.5.18+ / 3.2.1+ support
* credits go to Dominik Grothaus for providing SSHA256 support and fixing a few bugs
* credits go to Sören Eberhardt-Biermann for providing multi-host support
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\user_sql;
use OC\User\Backend;
use OCA\user_sql\HashAlgorithm\HashAlgorithm;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Util;
abstract class BackendUtility
{
protected $access;
/**
* constructor, make sure the subclasses call this one!
* @param Access $access an instance of Access for LDAP interaction
*/
public function __construct(Access $access)
{
$this->access = $access;
}
}
class OC_USER_SQL extends BackendUtility implements \OCP\IUserBackend,
\OCP\UserInterface
{
protected $cache;
protected $settings;
protected $helper;
protected $session_cache_name;
protected $ocConfig;
/**
* The default constructor. It loads the settings for the given domain
* and tries to connect to the database.
*/
public function __construct()
{
$memcache = \OC::$server->getMemCacheFactory();
if ($memcache->isAvailable()) {
$this->cache = $memcache->create();
}
$this->helper = new \OCA\user_sql\lib\Helper();
$domain = \OC::$server->getRequest()->getServerHost();
$this->settings = $this->helper->loadSettingsForDomain($domain);
$this->ocConfig = \OC::$server->getConfig();
$this->helper->connectToDb($this->settings);
$this->session_cache_name = 'USER_SQL_CACHE';
return false;
}
/**
* Sync the user's E-Mail address with the address stored by Nextcloud.
* We have three (four) sync modes:
* - none: Does nothing
* - initial: Do the sync only once from SQL -> Nextcloud
* - forcesql: The SQL database always wins and sync to Nextcloud
* - forceoc: Nextcloud always wins and syncs to SQL
*
* @param string $uid The user's ID to sync
* @return bool Success or Fail
*/
private function doEmailSync($uid)
{
Util::writeLog('OC_USER_SQL', "Entering doEmailSync for UID: $uid",
Util::DEBUG);
if ($this->settings['col_email'] === '') {
return false;
}
if ($this->settings['set_mail_sync_mode'] === 'none') {
return false;
}
$ocUid = $uid;
$uid = $this->doUserDomainMapping($uid);
$row = $this->helper->runQuery('getMail', array('uid' => $uid));
if ($row === false) {
return false;
}
$newMail = $row[$this->settings['col_email']];
$currMail = $this->ocConfig->getUserValue($ocUid,
'settings',
'email', '');
switch ($this->settings['set_mail_sync_mode']) {
case 'initial':
if ($currMail === '') {
$this->ocConfig->setUserValue($ocUid,
'settings',
'email',
$newMail);
}
break;
case 'forcesql':
//if($currMail !== $newMail)
$this->ocConfig->setUserValue($ocUid,
'settings',
'email',
$newMail);
break;
case 'forceoc':
if (($currMail !== '') && ($currMail !== $newMail)) {
$row = $this->helper->runQuery('setMail',
array(
'uid' => $uid,
'currMail' => $currMail
)
, true);
if ($row === false) {
Util::writeLog('OC_USER_SQL',
"Could not update E-Mail address in SQL database!",
Util::ERROR);
}
}
break;
}
return true;
}
/**
* This maps the username to the specified domain name.
* It can only append a default domain name.
*
* @param string $uid The UID to work with
* @return string The mapped UID
*/
private function doUserDomainMapping($uid)
{
$uid = trim($uid);
if ($this->settings['set_default_domain'] !== '') {
Util::writeLog('OC_USER_SQL', "Append default domain: " .
$this->settings['set_default_domain'], Util::DEBUG);
if (strpos($uid, '@') === false) {
$uid .= "@" . $this->settings['set_default_domain'];
}
}
$uid = strtolower($uid);
Util::writeLog('OC_USER_SQL', 'Returning mapped UID: ' . $uid,
Util::DEBUG);
return $uid;
}
/**
* Return the actions implemented by this backend
* @param $actions
* @return bool
*/
public function implementsActions($actions)
{
return (bool)((Backend::CHECK_PASSWORD
| Backend::GET_DISPLAYNAME
| Backend::COUNT_USERS
| ($this->settings['set_allow_pwchange'] === 'true' ?
Backend::SET_PASSWORD : 0)
| ($this->settings['set_enable_gethome'] === 'true' ?
Backend::GET_HOME : 0)
) & $actions);
}
/**
* Checks if this backend has user listing support
* @return bool
*/
public function hasUserListings()
{
return true;
}
/**
* Return the user's home directory, if enabled
* @param string $uid The user's ID to retrieve
* @return mixed The user's home directory or false
*/
public function getHome($uid)
{
Util::writeLog('OC_USER_SQL', "Entering getHome for UID: $uid",
Util::DEBUG);
if ($this->settings['set_enable_gethome'] !== 'true') {
return false;
}
$uidMapped = $this->doUserDomainMapping($uid);
$home = false;
switch ($this->settings['set_gethome_mode']) {
case 'query':
Util::writeLog('OC_USER_SQL',
"getHome with Query selected, running Query...",
Util::DEBUG);
$row = $this->helper->runQuery('getHome',
array('uid' => $uidMapped));
if ($row === false) {
Util::writeLog('OC_USER_SQL',
"Got no row, return false",
Util::DEBUG);
return false;
}
$home = $row[$this->settings['col_gethome']];
break;
case 'static':
Util::writeLog('OC_USER_SQL',
"getHome with static selected",
Util::DEBUG);
$home = $this->settings['set_gethome'];
$home = str_replace('%ud', $uidMapped, $home);
$home = str_replace('%u', $uid, $home);
$home = str_replace('%d',
$this->settings['set_default_domain'],
$home);
break;
}
Util::writeLog('OC_USER_SQL',
"Returning getHome for UID: $uid with Home $home",
Util::DEBUG);
return $home;
}
/**
* Create a new user account using this backend
* @return bool always false, as we can't create users
*/
public function createUser()
{
// Can't create user
Util::writeLog('OC_USER_SQL',
'Not possible to create local users from web' .
' frontend using SQL user backend', Util::ERROR);
return false;
}
/**
* Delete a user account using this backend
* @param string $uid The user's ID to delete
* @return bool always false, as we can't delete users
*/
public function deleteUser($uid)
{
// Can't delete user
Util::writeLog('OC_USER_SQL', 'Not possible to delete local users' .
' from web frontend using SQL user backend', Util::ERROR);
return false;
}
/**
* @return HashAlgorithm|bool
*/
private function getHashAlgorithmInstance() {
$cryptoType = $this->settings['set_crypt_type'];
require_once('HashAlgorithm/'. $cryptoType . '.php');
return call_user_func('OCA\\user_sql\\HashAlgorithm\\' . $cryptoType . "::getInstance");
}
/**
* Set (change) a user password
* This can be enabled/disabled in the settings (set_allow_pwchange)
*
* @param string $uid The user ID
* @param string $password The user's new password
* @return bool The return status
*/
public function setPassword($uid, $password)
{
// Update the user's password - this might affect other services, that
// use the same database, as well
Util::writeLog('OC_USER_SQL', "Entering setPassword for UID: $uid",
Util::DEBUG);
if ($this->settings['set_allow_pwchange'] !== 'true') {
return false;
}
$uid = $this->doUserDomainMapping($uid);
$row = $this->helper->runQuery('getPass', array('uid' => $uid));
if ($row === false) {
return false;
}
$hashAlgorithm = $this->getHashAlgorithmInstance();
if ($hashAlgorithm === false) {
return false;
}
$enc_password = $hashAlgorithm->getPasswordHash($password);
if ($enc_password === false) {
return false;
}
/*$old_password = $row[$this->settings['col_password']];
// Added and disabled updating passwords for Drupal 7 WD 2018-01-04
if ($this->settings['set_crypt_type'] === 'drupal') {
return false;
} elseif ($this->settings['set_crypt_type'] === 'joomla2') {
if (!class_exists('\PasswordHash')) {
require_once('PasswordHash.php');
}
$hasher = new \PasswordHash(10, true);
$enc_password = $hasher->HashPassword($password);
}
// Redmine stores the salt separatedly, this doesn't play nice with
// the way we check passwords
elseif ($this->settings['set_crypt_type'] === 'redmine') {
$salt = $this->helper->runQuery('getRedmineSalt',
array('uid' => $uid));
if (!$salt) {
return false;
}
$enc_password = sha1($salt['salt'] . sha1($password));
} elseif ($this->settings['set_crypt_type'] === 'sha1') {
$enc_password = sha1($password);
} elseif ($this->settings['set_crypt_type'] === 'system') {
$prefix = substr($old_password, 0, 2);
if ($prefix === "$2") {
$enc_password = $this->pw_hash($password);
} else {
if (($prefix === "$1") or ($prefix[0] != "$")) //old md5 or DES
{
//Update encryption algorithm
$prefix = "$6"; //change to sha512
}
$newsalt = $this->create_systemsalt();
$enc_password = crypt($password, $prefix . "$" . $newsalt);
}
} elseif ($this->settings['set_crypt_type'] === 'password_hash') {
$enc_password = $this->pw_hash($password);
} elseif ($this->settings['set_crypt_type'] === 'courier_md5') {
$enc_password = '{MD5}' . OC_USER_SQL::hex_to_base64(md5($password));
} elseif ($this->settings['set_crypt_type'] === 'courier_md5raw') {
$enc_password = '{MD5RAW}' . md5($password);
} elseif ($this->settings['set_crypt_type'] === 'courier_sha1') {
$enc_password = '{SHA}' . OC_USER_SQL::hex_to_base64(sha1($password));
} elseif ($this->settings['set_crypt_type'] === 'courier_sha256') {
$enc_password = '{SHA256}' . OC_USER_SQL::hex_to_base64(hash('sha256', $password, false));
} else {
$enc_password = $this->pacrypt($password, $old_password);
}*/
$res = $this->helper->runQuery('setPass',
array('uid' => $uid, 'enc_password' => $enc_password),
true);
if ($res === false) {
Util::writeLog('OC_USER_SQL', "Could not update password!",
Util::ERROR);
return false;
}
Util::writeLog('OC_USER_SQL',
"Updated password successfully, return true",
Util::DEBUG);
return true;
}
/**
* Check if the password is correct
* @param string $uid The username
* @param string $password The password
* @return bool true/false
*
* Check if the password is correct without logging in the user
*/
public function checkPassword($uid, $password)
{
Util::writeLog('OC_USER_SQL',
"Entering checkPassword() for UID: $uid",
Util::DEBUG);
$uid = $this->doUserDomainMapping($uid);
$row = $this->helper->runQuery('getPass', array('uid' => $uid));
if ($row === false) {
Util::writeLog('OC_USER_SQL', "Got no row, return false", Util::DEBUG);
return false;
}
$db_pass = $row[$this->settings['col_password']];
Util::writeLog('OC_USER_SQL', "Encrypting and checking password",
Util::DEBUG);
$hashAlgorithm = $this->getHashAlgorithmInstance();
if ($hashAlgorithm === false) {
return false;
}
$ret = $hashAlgorithm->checkPassword($password, $db_pass);
/*// Added handling for Drupal 7 passwords WD 2018-01-04
if ($this->settings['set_crypt_type'] === 'drupal') {
if (!function_exists('user_check_password')) {
require_once('drupal.php');
}
$ret = user_check_password($password, $db_pass);
}
// Joomla 2.5.18 switched to phPass, which doesn't play nice with the
// way we check passwords
elseif ($this->settings['set_crypt_type'] === 'joomla2') {
if (!class_exists('\PasswordHash')) {
require_once('PasswordHash.php');
}
$hasher = new \PasswordHash(10, true);
$ret = $hasher->CheckPassword($password, $db_pass);
} elseif ($this->settings['set_crypt_type'] === 'password_hash') {
$ret = password_verify($password, $db_pass);
}
// Redmine stores the salt separatedly, this doesn't play nice with the
// way we check passwords
elseif ($this->settings['set_crypt_type'] === 'redmine') {
$salt = $this->helper->runQuery('getRedmineSalt',
array('uid' => $uid));
if (!$salt) {
return false;
}
$ret = sha1($salt['salt'] . sha1($password)) === $db_pass;
} elseif ($this->settings['set_crypt_type'] == 'sha1') {
$ret = $this->hash_equals(sha1($password), $db_pass);
} elseif ($this->settings['set_crypt_type'] === 'courier_md5') {
$ret = '{MD5}' . OC_USER_SQL::hex_to_base64(md5($password)) === $db_pass;
} elseif ($this->settings['set_crypt_type'] === 'courier_md5raw') {
$ret = '{MD5RAW}' . md5($password) === $db_pass;
} elseif ($this->settings['set_crypt_type'] === 'courier_sha1') {
$ret = '{SHA}' . OC_USER_SQL::hex_to_base64(sha1($password)) === $db_pass;
} elseif ($this->settings['set_crypt_type'] === 'courier_sha256') {
$ret = '{SHA256}' . OC_USER_SQL::hex_to_base64(hash('sha256', $password, false)) === $db_pass;
} else {
// $ret = $this -> pacrypt($password, $db_pass) === $db_pass;
$ret = $this->hash_equals($this->pacrypt($password, $db_pass),
$db_pass);
}*/
if ($ret) {
Util::writeLog('OC_USER_SQL',
"Passwords matching, return true",
Util::DEBUG);
if ($this->settings['set_strip_domain'] === 'true') {
$uid = explode("@", $uid);
$uid = $uid[0];
}
return $uid;
} else {
Util::writeLog('OC_USER_SQL',
"Passwords do not match, return false",
Util::DEBUG);
return false;
}
}
/**
* Count the number of users
* @return int The user count
*/
public function countUsers()
{
Util::writeLog('OC_USER_SQL', "Entering countUsers()",
Util::DEBUG);
$search = "%" . $this->doUserDomainMapping("");
$userCount = $this->helper->runQuery('countUsers',
array('search' => $search));
if ($userCount === false) {
$userCount = 0;
} else {
$userCount = reset($userCount);
}
Util::writeLog('OC_USER_SQL', "Return usercount: " . $userCount,
Util::DEBUG);
return $userCount;
}
/**
* Get a list of all users
* @param string $search The search term (can be empty)
* @param int $limit The search limit (can be null)
* @param int $offset The search offset (can be null)
* @return array with all uids
*/
public function getUsers($search = '', $limit = null, $offset = null)
{
Util::writeLog('OC_USER_SQL',
"Entering getUsers() with Search: $search, " .
"Limit: $limit, Offset: $offset", Util::DEBUG);
$users = array();
if ($search !== '') {
$search = "%" . $this->doUserDomainMapping($search . "%") . "%";
} else {
$search = "%" . $this->doUserDomainMapping("") . "%";
}
$rows = $this->helper->runQuery('getUsers',
array('search' => $search),
false,
true,
array(
'limit' => $limit,
'offset' => $offset
));
if ($rows === false) {
return array();
}
foreach ($rows as $row) {
$uid = $row[$this->settings['col_username']];
if ($this->settings['set_strip_domain'] === 'true') {
$uid = explode("@", $uid);
$uid = $uid[0];
}
$users[] = strtolower($uid);
}
Util::writeLog('OC_USER_SQL', "Return list of results",
Util::DEBUG);
return $users;
}
/**
* Check if a user exists
* @param string $uid the username
* @return boolean
*/
public function userExists($uid)
{
$cacheKey = 'sql_user_exists_' . $uid;
$cacheVal = $this->getCache($cacheKey);
Util::writeLog('OC_USER_SQL',
"userExists() for UID: $uid cacheVal: $cacheVal",
Util::DEBUG);
if (!is_null($cacheVal)) {
return (bool)$cacheVal;
}
Util::writeLog('OC_USER_SQL',
"Entering userExists() for UID: $uid",
Util::DEBUG);
// Only if the domain is removed for internal user handling,
// we should add the domain back when checking existance
if ($this->settings['set_strip_domain'] === 'true') {
$uid = $this->doUserDomainMapping($uid);
}
$exists = (bool)$this->helper->runQuery('userExists',
array('uid' => $uid));;
$this->setCache($cacheKey, $exists, 60);
if (!$exists) {
Util::writeLog('OC_USER_SQL',
"Empty row, user does not exists, return false",
Util::DEBUG);
return false;
} else {
Util::writeLog('OC_USER_SQL', "User exists, return true",
Util::DEBUG);
return true;
}
}
/**
* Get the display name of the user
* @param string $uid The user ID
* @return mixed The user's display name or FALSE
*/
public function getDisplayName($uid)
{
Util::writeLog('OC_USER_SQL',
"Entering getDisplayName() for UID: $uid",
Util::DEBUG);
$this->doEmailSync($uid);
$uid = $this->doUserDomainMapping($uid);
if (!$this->userExists($uid)) {
return false;
}
$row = $this->helper->runQuery('getDisplayName',
array('uid' => $uid));
if (!$row) {
Util::writeLog('OC_USER_SQL',
"Empty row, user has no display name or " .
"does not exist, return false",
Util::DEBUG);
return false;
} else {
Util::writeLog('OC_USER_SQL',
"User exists, return true",
Util::DEBUG);
$displayName = $row[$this->settings['col_displayname']];
return $displayName;;
}
return false;
}
public function getDisplayNames($search = '', $limit = null, $offset = null)
{
$uids = $this->getUsers($search, $limit, $offset);
$displayNames = array();
foreach ($uids as $uid) {
$displayNames[$uid] = $this->getDisplayName($uid);
}
return $displayNames;
}
/**
* Returns the backend name
* @return string
*/
public function getBackendName()
{
return 'user_sql';
}
/**
* The following functions were directly taken from PostfixAdmin and just
* slightly modified
* to suit our needs.
* Encrypt a password,using the apparopriate hashing mechanism as defined in
* config.inc.php ($this->crypt_type).
* When wanting to compare one pw to another, it's necessary to provide the
* salt used - hence
* the second parameter ($pw_db), which is the existing hash from the DB.
*
* @param string $pw cleartext password
* @param string $pw_db encrypted password from database
* @return string encrypted password.
*/
/*private function pacrypt($pw, $pw_db = "")
{
Util::writeLog('OC_USER_SQL', "Entering private pacrypt()",
Util::DEBUG);
$pw = stripslashes($pw);
$password = "";
$salt = "";
if ($this->settings['set_crypt_type'] === 'md5crypt') {
$split_salt = preg_split('/\$/', $pw_db);
if (isset($split_salt[2])) {
$salt = $split_salt[2];
}
$password = $this->md5crypt($pw, $salt);
} elseif ($this->settings['set_crypt_type'] === 'md5') {
$password = md5($pw);
} elseif ($this->settings['set_crypt_type'] === 'system') {
// We never generate salts, as user creation is not allowed here
$password = crypt($pw, $pw_db);
} elseif ($this->settings['set_crypt_type'] === 'cleartext') {
$password = $pw;
}
// See
// https://sourceforge.net/tracker/?func=detail&atid=937966&aid=1793352&group_id=191583
// this is apparently useful for pam_mysql etc.
elseif ($this->settings['set_crypt_type'] === 'mysql_encrypt') {
if ($pw_db !== "") {
$salt = substr($pw_db, 0, 2);
$row = $this->helper->runQuery('mysqlEncryptSalt',
array('pw' => $pw, 'salt' => $salt));
} else {
$row = $this->helper->runQuery('mysqlEncrypt',
array('pw' => $pw));
}
if ($row === false) {
return false;
}
$password = $row[0];
} elseif ($this->settings['set_crypt_type'] === 'mysql_password') {
$row = $this->helper->runQuery('mysqlPassword',
array('pw' => $pw));
if ($row === false) {
return false;
}
$password = $row[0];
} // The following is by Frédéric France
elseif ($this->settings['set_crypt_type'] === 'joomla') {
$split_salt = preg_split('/:/', $pw_db);
if (isset($split_salt[1])) {
$salt = $split_salt[1];
}
$password = ($salt) ? md5($pw . $salt) : md5($pw);
$password .= ':' . $salt;
} elseif ($this->settings['set_crypt_type'] === 'ssha256') {
$salted_password = base64_decode(
preg_replace('/{SSHA256}/i', '', $pw_db));
$salt = substr($salted_password, -(strlen($salted_password) - 32));
$password = $this->ssha256($pw, $salt);
} else {
Util::writeLog('OC_USER_SQL',
"unknown/invalid crypt_type settings: " .
$this->settings['set_crypt_type'],
Util::ERROR);
die('unknown/invalid Encryption type setting: ' .
$this->settings['set_crypt_type']);
}
Util::writeLog('OC_USER_SQL', "pacrypt() done, return",
Util::DEBUG);
return $password;
}*/
/**
* md5crypt
* Creates MD5 encrypted password
* @param string $pw The password to encrypt
* @param string $salt The salt to use
* @param string $magic ?
* @return string The encrypted password
*/
/*private function md5crypt($pw, $salt = "", $magic = "")
{
$MAGIC = "$1$";
if ($magic === "") {
$magic = $MAGIC;
}
if ($salt === "") {
$salt = $this->create_md5salt();
}
$slist = explode("$", $salt);
if ($slist[0] === "1") {
$salt = $slist[1];
}
$salt = substr($salt, 0, 8);
$ctx = $pw . $magic . $salt;
$final = $this->pahex2bin(md5($pw . $salt . $pw));
for ($i = strlen($pw); $i > 0; $i -= 16) {
if ($i > 16) {
$ctx .= substr($final, 0, 16);
} else {
$ctx .= substr($final, 0, $i);
}
}
$i = strlen($pw);
while ($i > 0) {
if ($i & 1) {
$ctx .= chr(0);
} else {
$ctx .= $pw[0];
}
$i = $i >> 1;
}
$final = $this->pahex2bin(md5($ctx));
for ($i = 0; $i < 1000; $i++) {
$ctx1 = "";
if ($i & 1) {
$ctx1 .= $pw;
} else {
$ctx1 .= substr($final, 0, 16);
}
if ($i % 3) {
$ctx1 .= $salt;
}
if ($i % 7) {
$ctx1 .= $pw;
}
if ($i & 1) {
$ctx1 .= substr($final, 0, 16);
} else {
$ctx1 .= $pw;
}
$final = $this->pahex2bin(md5($ctx1));
}
$passwd = "";
$passwd .= $this->to64(((ord($final[0]) << 16) |
(ord($final[6]) << 8) | (ord($final[12]))), 4);
$passwd .= $this->to64(((ord($final[1]) << 16) |
(ord($final[7]) << 8) | (ord($final[13]))), 4);
$passwd .= $this->to64(((ord($final[2]) << 16) |
(ord($final[8]) << 8) | (ord($final[14]))), 4);
$passwd .= $this->to64(((ord($final[3]) << 16) |
(ord($final[9]) << 8) | (ord($final[15]))), 4);
$passwd .= $this->to64(((ord($final[4]) << 16) |
(ord($final[10]) << 8) | (ord($final[5]))), 4);
$passwd .= $this->to64(ord($final[11]), 2);
return "$magic$salt\$$passwd";
}*/
/**
* Create a new salte
* @return string The salt
*/
/*private function create_md5salt()
{
srand((double)microtime() * 1000000);
$salt = substr(md5(rand(0, 9999999)), 0, 8);
return $salt;
}*/
/**
* Encrypt using SSHA256 algorithm
* @param string $pw The password
* @param string $salt The salt to use
* @return string The hashed password, prefixed by {SSHA256}
*/
/*private function ssha256($pw, $salt)
{
return '{SSHA256}' . base64_encode(hash('sha256', $pw . $salt, true) . $salt);
}*/
/**
* PostfixAdmin's hex2bin function
* @param string $str The string to convert
* @return string The converted string
*/
/*private function pahex2bin($str)
{
if (function_exists('hex2bin')) {
return hex2bin($str);
} else {
$len = strlen($str);
$nstr = "";
for ($i = 0; $i < $len; $i += 2) {
$num = sscanf(substr($str, $i, 2), "%x");
$nstr .= chr($num[0]);
}
return $nstr;
}
}*/
/**
* Convert to 64?
*/
/*private function to64($v, $n)
{
$ITOA64 =
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
$ret = "";
while (($n - 1) >= 0) {
$n--;
$ret .= $ITOA64[$v & 0x3f];
$v = $v >> 6;
}
return $ret;
}*/
/**
* Store a value in memcache or the session, if no memcache is available
* @param string $key The key
* @param mixed $value The value to store
* @param int $ttl (optional) defaults to 3600 seconds.
*/
private function setCache($key, $value, $ttl = 3600)
{
if ($this->cache === null) {
$_SESSION[$this->session_cache_name][$key] = array(
'value' => $value,
'time' => time(),
'ttl' => $ttl,
);
} else {
$this->cache->set($key, $value, $ttl);
}
}
/**
* Fetch a value from memcache or session, if memcache is not available.
* Returns NULL if there's no value stored or the value expired.
* @param string $key
* @return mixed|NULL
*/
private function getCache($key)
{
$retVal = null;
if ($this->cache === null) {
if (isset($_SESSION[$this->session_cache_name],
$_SESSION[$this->session_cache_name][$key])) {
$value = $_SESSION[$this->session_cache_name][$key];
if (time() < $value['time'] + $value['ttl']) {
$retVal = $value['value'];
}
}
} else {
$retVal = $this->cache->get($key);
}
return $retVal;
}
/*private function create_systemsalt($length = 20)
{
$fp = fopen('/dev/urandom', 'r');
$randomString = fread($fp, $length);
fclose($fp);
$salt = base64_encode($randomString);
return $salt;
}*/
/*private function pw_hash($password)
{
$options = [
'cost' => 10,
];
return password_hash($password, PASSWORD_BCRYPT, $options);
}*/
/*function hash_equals($a, $b)
{
$a_length = strlen($a);
if ($a_length !== strlen($b)) {
return false;
}
$result = 0;
// Do not attempt to "optimize" this.
for ($i = 0; $i < $a_length; $i++) {
$result |= ord($a[$i]) ^ ord($b[$i]);
}
//Hide the length of the string
$additional_length = 200 - ($a_length % 200);
$tmp = 0;
$c = "abCD";
for ($i = 0; $i < $additional_length; $i++) {
$tmp |= ord($c[0]) ^ ord($c[0]);
}
return $result === 0;
}*/
/*private static function hex_to_base64($hex)
{
$hex_chr = '';
foreach (str_split($hex, 2) as $hexpair) {
$hex_chr .= chr(hexdec($hexpair));
}
return base64_encode($hex_chr);
}*/
}