Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .htaccess
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<FilesMatch "\.(css|js|svg|gif)$">
Header set Cache-Control "max-age=15778463"
</FilesMatch>

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine.

# Let browsers cache WOFF files for a week
<FilesMatch "\.woff$">
Header set Cache-Control "max-age=604800"
Expand Down
2 changes: 1 addition & 1 deletion lib/private/DB/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ public function inTransaction() {
}

/**
* Espace a parameter to be used in a LIKE query
* Escape a parameter to be used in a LIKE query
*
* @param string $param
* @return string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

use Doctrine\DBAL\Query\Expression\ExpressionBuilder as DoctrineExpressionBuilder;
use OC\DB\QueryBuilder\CompositeExpression;
use OC\DB\QueryBuilder\FunctionBuilder\FunctionBuilder;
use OC\DB\QueryBuilder\Literal;
use OC\DB\QueryBuilder\QueryFunction;
use OC\DB\QueryBuilder\QuoteHelper;
Expand All @@ -45,15 +46,20 @@ class ExpressionBuilder implements IExpressionBuilder {
/** @var IDBConnection */
protected $connection;

/** @var FunctionBuilder */
protected $functionBuilder;

/**
* Initializes a new <tt>ExpressionBuilder</tt>.
*
* @param \OCP\IDBConnection $connection
* @param IDBConnection $connection
* @param IQueryBuilder $queryBuilder
*/
public function __construct(IDBConnection $connection) {
public function __construct(IDBConnection $connection, IQueryBuilder $queryBuilder) {
$this->connection = $connection;
$this->helper = new QuoteHelper();
$this->expressionBuilder = new DoctrineExpressionBuilder($connection);
$this->functionBuilder = $queryBuilder->func();
}

/**
Expand Down Expand Up @@ -298,9 +304,7 @@ public function like($x, $y, $type = null) {
* @since 9.0.0
*/
public function iLike($x, $y, $type = null) {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
return $this->expressionBuilder->comparison("LOWER($x)", 'LIKE', "LOWER($y)");
return $this->expressionBuilder->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@


use OC\DB\Connection;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

class MySqlExpressionBuilder extends ExpressionBuilder {
Expand All @@ -34,9 +35,10 @@ class MySqlExpressionBuilder extends ExpressionBuilder {

/**
* @param \OCP\IDBConnection|Connection $connection
* @param IQueryBuilder $queryBuilder
*/
public function __construct(IDBConnection $connection) {
parent::__construct($connection);
public function __construct(IDBConnection $connection, IQueryBuilder $queryBuilder) {
parent::__construct($connection, $queryBuilder);

$params = $connection->getParams();
$this->charset = isset($params['charset']) ? $params['charset'] : 'utf8';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ class SqliteExpressionBuilder extends ExpressionBuilder {
public function like($x, $y, $type = null) {
return parent::like($x, $y, $type) . " ESCAPE '\\'";
}

public function iLike($x, $y, $type = null) {
return $this->like($this->functionBuilder->lower($x), $this->functionBuilder->lower($y), $type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,8 @@ public function substring($input, $start, $length = null) {
public function sum($field) {
return new QueryFunction('SUM(' . $this->helper->quoteColumnName($field) . ')');
}

public function lower($field) {
return new QueryFunction('LOWER(' . $this->helper->quoteColumnName($field) . ')');
}
}
10 changes: 5 additions & 5 deletions lib/private/DB/QueryBuilder/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,15 @@ public function automaticTablePrefix($enabled) {
*/
public function expr() {
if ($this->connection instanceof OracleConnection) {
return new OCIExpressionBuilder($this->connection);
return new OCIExpressionBuilder($this->connection, $this);
} else if ($this->connection->getDatabasePlatform() instanceof PostgreSqlPlatform) {
return new PgSqlExpressionBuilder($this->connection);
return new PgSqlExpressionBuilder($this->connection, $this);
} else if ($this->connection->getDatabasePlatform() instanceof MySqlPlatform) {
return new MySqlExpressionBuilder($this->connection);
return new MySqlExpressionBuilder($this->connection, $this);
} else if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) {
return new SqliteExpressionBuilder($this->connection);
return new SqliteExpressionBuilder($this->connection, $this);
} else {
return new ExpressionBuilder($this->connection);
return new ExpressionBuilder($this->connection, $this);
}
}

Expand Down
79 changes: 44 additions & 35 deletions lib/private/User/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

/*
*
* The following SQL statement is just a help for developers and will not be
Expand All @@ -56,6 +57,7 @@
namespace OC\User;

use OC\Cache\CappedMemoryCache;
use OC\DB\QueryBuilder\Literal;
use OCP\IUserBackend;
use OCP\Util;
use Symfony\Component\EventDispatcher\EventDispatcher;
Expand Down Expand Up @@ -83,6 +85,7 @@ public function __construct($eventDispatcher = null) {

/**
* Create a new user
*
* @param string $uid The username of the user to create
* @param string $password The password of the new user
* @return bool
Expand Down Expand Up @@ -112,6 +115,7 @@ public function createUser($uid, $password) {

/**
* delete a user
*
* @param string $uid The username of the user to delete
* @return bool
*
Expand All @@ -131,6 +135,7 @@ public function deleteUser($uid) {

/**
* Set password
*
* @param string $uid The username
* @param string $password The new password
* @return bool
Expand All @@ -152,6 +157,7 @@ public function setPassword($uid, $password) {

/**
* Set display name
*
* @param string $uid The username
* @param string $displayName The new display name
* @return bool
Expand All @@ -172,6 +178,7 @@ public function setDisplayName($uid, $displayName) {

/**
* get display name of the user
*
* @param string $uid user ID of the user
* @return string display name
*/
Expand All @@ -189,20 +196,29 @@ public function getDisplayName($uid) {
* @return array an array of all displayNames (value) and the corresponding uids (key)
*/
public function getDisplayNames($search = '', $limit = null, $offset = null) {
$parameters = [];
$searchLike = '';
if ($search !== '') {
$parameters[] = '%' . \OC::$server->getDatabaseConnection()->escapeLikeParameter($search) . '%';
$parameters[] = '%' . \OC::$server->getDatabaseConnection()->escapeLikeParameter($search) . '%';
$searchLike = ' WHERE LOWER(`displayname`) LIKE LOWER(?) OR '
. 'LOWER(`uid`) LIKE LOWER(?)';
}
$connection = \OC::$server->getDatabaseConnection();

$query = $connection->getQueryBuilder();

$query->select('uid', 'displayname')
->from('users', 'u')
->leftJoin('u', 'preferences', 'p', $query->expr()->andX(
$query->expr()->eq('userid', 'uid')),
$query->expr()->eq('appid', new Literal('settings')),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Literal cannot be hidden from consumers?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All plain strings are used as column names

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean whether everywhere where we use strings in queries they need to be instances of Literal or whether it can be abstracted away?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to use Literal to explicitly tell the query builder that the string is safe to use

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

$query->expr()->eq('configkey', new Literal('email'))
)
// sqlite doesn't like re-using a single named parameter here
->where($query->expr()->iLike('uid', $query->createPositionalParameter('%' . $connection->escapeLikeParameter($search) . '%')))
->orWhere($query->expr()->iLike('displayname', $query->createPositionalParameter('%' . $connection->escapeLikeParameter($search) . '%')))
->orWhere($query->expr()->iLike('configvalue', $query->createPositionalParameter('%' . $connection->escapeLikeParameter($search) . '%')))
->orderBy($query->func()->lower('displayname'), 'ASC')
->orderBy($query->func()->lower('uid'), 'ASC')
->setMaxResults($limit)
->setFirstResult($offset);

$displayNames = array();
$query = \OC_DB::prepare('SELECT `uid`, `displayname` FROM `*PREFIX*users`'
. $searchLike .' ORDER BY LOWER(`displayname`), LOWER(`uid`) ASC', $limit, $offset);
$result = $query->execute($parameters);
while ($row = $result->fetchRow()) {
$result = $query->execute();
$displayNames = [];
while ($row = $result->fetch()) {
$displayNames[$row['uid']] = $row['displayname'];
}

Expand All @@ -211,6 +227,7 @@ public function getDisplayNames($search = '', $limit = null, $offset = null) {

/**
* Check if the password is correct
*
* @param string $uid The username
* @param string $password The password
* @return string
Expand All @@ -226,8 +243,8 @@ public function checkPassword($uid, $password) {
if ($row) {
$storedHash = $row['password'];
$newHash = '';
if(\OC::$server->getHasher()->verify($password, $storedHash, $newHash)) {
if(!empty($newHash)) {
if (\OC::$server->getHasher()->verify($password, $storedHash, $newHash)) {
if (!empty($newHash)) {
$this->setPassword($uid, $password);
}
return $row['uid'];
Expand All @@ -240,15 +257,16 @@ public function checkPassword($uid, $password) {

/**
* Load an user in the cache
*
* @param string $uid the username
* @return boolean true if user was found, false otherwise
*/
private function loadUser($uid) {
$uid = (string) $uid;
$uid = (string)$uid;
if (!isset($this->cache[$uid])) {
//guests $uid could be NULL or ''
if ($uid === '') {
$this->cache[$uid]=false;
$this->cache[$uid] = false;
return true;
}

Expand Down Expand Up @@ -285,26 +303,15 @@ private function loadUser($uid) {
* @return string[] an array of all uids
*/
public function getUsers($search = '', $limit = null, $offset = null) {
$parameters = [];
$searchLike = '';
if ($search !== '') {
$parameters[] = '%' . \OC::$server->getDatabaseConnection()->escapeLikeParameter($search) . '%';
$searchLike = ' WHERE LOWER(`uid`) LIKE LOWER(?)';
$parameters[] = '%' . \OC::$server->getDatabaseConnection()->escapeLikeParameter($search) . '%';
$searchLike .= ' OR LOWER(`displayname`) LIKE LOWER(?)';
}

$query = \OC_DB::prepare('SELECT `uid` FROM `*PREFIX*users`' . $searchLike . ' ORDER BY LOWER(`uid`) ASC', $limit, $offset);
$result = $query->execute($parameters);
$users = array();
while ($row = $result->fetchRow()) {
$users[] = $row['uid'];
}
return $users;
$users = $this->getDisplayNames($search, $limit, $offset);
$userIds = array_keys($users);
sort($userIds, SORT_STRING | SORT_FLAG_CASE);
return $userIds;
}

/**
* check if a user exists
*
* @param string $uid the username
* @return boolean
*/
Expand All @@ -315,6 +322,7 @@ public function userExists($uid) {

/**
* get the user's home directory
*
* @param string $uid the username
* @return string|false
*/
Expand Down Expand Up @@ -364,14 +372,15 @@ public function loginName2UserName($loginName) {

/**
* Backend name to be shown in user management
*
* @return string the name of the backend to be shown
*/
public function getBackendName(){
public function getBackendName() {
return 'Database';
}

public static function preLoginNameUsedAsUserName($param) {
if(!isset($param['uid'])) {
if (!isset($param['uid'])) {
throw new \Exception('key uid is expected to be set in $param');
}

Expand Down
9 changes: 9 additions & 0 deletions lib/public/DB/QueryBuilder/IFunctionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,13 @@ public function substring($input, $start, $length = null);
* @since 12.0.0
*/
public function sum($field);

/**
* Transforms a string field or value to lower case
*
* @param mixed $field
* @return IQueryFunction
* @since 13.0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #8696

*/
public function lower($field);
}
4 changes: 3 additions & 1 deletion tests/lib/DB/QueryBuilder/ExpressionBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ protected function setUp() {

$this->connection = \OC::$server->getDatabaseConnection();

$this->expressionBuilder = new ExpressionBuilder($this->connection);
$queryBuilder = $this->createMock(IQueryBuilder::class);

$this->expressionBuilder = new ExpressionBuilder($this->connection, $queryBuilder);

$this->doctrineExpressionBuilder = new DoctrineExpressionBuilder($this->connection);
}
Expand Down
10 changes: 10 additions & 0 deletions tests/lib/DB/QueryBuilder/FunctionBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,14 @@ public function testSubstringNoLength() {

$this->assertEquals('oobar', $query->execute()->fetchColumn());
}

public function testLower() {
$query = $this->connection->getQueryBuilder();

$query->select($query->func()->lower($query->createNamedParameter('FooBar')));
$query->from('appconfig')
->setMaxResults(1);

$this->assertEquals('foobar', $query->execute()->fetchColumn());
}
}
12 changes: 10 additions & 2 deletions tests/lib/User/Backend.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,23 @@ public function testSearch() {
$name1 = 'foobarbaz';
$name2 = 'bazbarfoo';
$name3 = 'notme';
$name4 = 'under_score';

$this->backend->createUser($name1, 'pass1');
$this->backend->createUser($name2, 'pass2');
$this->backend->createUser($name3, 'pass3');
$this->backend->createUser($name4, 'pass4');

$result = $this->backend->getUsers('bar');
$this->assertSame(2, count($result));
$this->assertCount(2, $result);

$result = $this->backend->getDisplayNames('bar');
$this->assertSame(2, count($result));
$this->assertCount(2, $result);

$result = $this->backend->getUsers('under_');
$this->assertCount(1, $result);

$result = $this->backend->getUsers('not_');
$this->assertCount(0, $result);
}
}
Loading