Skip to content

Commit

Permalink
make the email match optional when searching for a user or a display …
Browse files Browse the repository at this point in the history
…name

Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
  • Loading branch information
julien-nc committed Dec 19, 2024
1 parent f58c5c3 commit d5119d6
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 29 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@ You can disable these check with these config value (in config.php):
],
```

### Disable the user search by email

This app can stop matching users (when a user search is performed in Nextcloud) by setting this config.php value:
``` php
'user_oidc' => [
'user_search_match_emails' => false,
],
```

## Building the app

Requirements for building:
Expand Down
82 changes: 54 additions & 28 deletions lib/Db/UserMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use OCP\AppFramework\Db\IMapperException;
use OCP\AppFramework\Db\QBMapper;
use OCP\Cache\CappedMemoryCache;
use OCP\IConfig;
use OCP\IDBConnection;

/**
Expand All @@ -24,6 +25,7 @@ class UserMapper extends QBMapper {
public function __construct(
IDBConnection $db,
private LocalIdService $idService,
private IConfig $config,
) {
parent::__construct($db, 'user_oidc', User::class);
$this->userCache = new CappedMemoryCache();
Expand Down Expand Up @@ -57,41 +59,65 @@ public function getUser(string $uid): User {
public function find(string $search, $limit = null, $offset = null): array {
$qb = $this->db->getQueryBuilder();

$qb->select('user_id', 'display_name')
->from($this->getTableName(), 'u')
->leftJoin('u', 'preferences', 'p', $qb->expr()->andX(
$qb->expr()->eq('userid', 'user_id'),
$qb->expr()->eq('appid', $qb->expr()->literal('settings')),
$qb->expr()->eq('configkey', $qb->expr()->literal('email')))
)
->where($qb->expr()->iLike('user_id', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orWhere($qb->expr()->iLike('display_name', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orWhere($qb->expr()->iLike('configvalue', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orderBy($qb->func()->lower('user_id'), 'ASC')
->setMaxResults($limit)
->setFirstResult($offset);
$oidcSystemConfig = $this->config->getSystemValue('user_oidc', []);
$matchEmails = !isset($oidcSystemConfig['user_search_match_emails']) || $oidcSystemConfig['user_search_match_emails'] === true;
if ($matchEmails) {
$qb->select('user_id', 'display_name')
->from($this->getTableName(), 'u')
->leftJoin('u', 'preferences', 'p', $qb->expr()->andX(
$qb->expr()->eq('userid', 'user_id'),
$qb->expr()->eq('appid', $qb->expr()->literal('settings')),
$qb->expr()->eq('configkey', $qb->expr()->literal('email')))
)
->where($qb->expr()->iLike('user_id', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orWhere($qb->expr()->iLike('display_name', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orWhere($qb->expr()->iLike('configvalue', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orderBy($qb->func()->lower('user_id'), 'ASC')
->setMaxResults($limit)
->setFirstResult($offset);
} else {
$qb->select('user_id', 'display_name')
->from($this->getTableName())
->where($qb->expr()->iLike('user_id', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orWhere($qb->expr()->iLike('display_name', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orderBy($qb->func()->lower('user_id'), 'ASC')
->setMaxResults($limit)
->setFirstResult($offset);
}

return $this->findEntities($qb);
}

public function findDisplayNames(string $search, $limit = null, $offset = null): array {
$qb = $this->db->getQueryBuilder();

$qb->select('user_id', 'display_name')
->from($this->getTableName(), 'u')
->leftJoin('u', 'preferences', 'p', $qb->expr()->andX(
$qb->expr()->eq('userid', 'user_id'),
$qb->expr()->eq('appid', $qb->expr()->literal('settings')),
$qb->expr()->eq('configkey', $qb->expr()->literal('email')))
)
->where($qb->expr()->iLike('user_id', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orWhere($qb->expr()->iLike('display_name', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orWhere($qb->expr()->iLike('configvalue', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orderBy($qb->func()->lower('user_id'), 'ASC')
->setMaxResults($limit)
->setFirstResult($offset);

$result = $qb->execute();
$oidcSystemConfig = $this->config->getSystemValue('user_oidc', []);
$matchEmails = !isset($oidcSystemConfig['user_search_match_emails']) || $oidcSystemConfig['user_search_match_emails'] === true;
if ($matchEmails) {
$qb->select('user_id', 'display_name')
->from($this->getTableName(), 'u')
->leftJoin('u', 'preferences', 'p', $qb->expr()->andX(
$qb->expr()->eq('userid', 'user_id'),
$qb->expr()->eq('appid', $qb->expr()->literal('settings')),
$qb->expr()->eq('configkey', $qb->expr()->literal('email')))
)
->where($qb->expr()->iLike('user_id', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orWhere($qb->expr()->iLike('display_name', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orWhere($qb->expr()->iLike('configvalue', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orderBy($qb->func()->lower('user_id'), 'ASC')
->setMaxResults($limit)
->setFirstResult($offset);
} else {
$qb->select('user_id', 'display_name')
->from($this->getTableName())
->where($qb->expr()->iLike('user_id', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orWhere($qb->expr()->iLike('display_name', $qb->createPositionalParameter('%' . $this->db->escapeLikeParameter($search) . '%')))
->orderBy($qb->func()->lower('user_id'), 'ASC')
->setMaxResults($limit)
->setFirstResult($offset);
}

$result = $qb->executeQuery();
$displayNames = [];
while ($row = $result->fetch()) {
$displayNames[(string)$row['user_id']] = (string)$row['display_name'];
Expand Down
7 changes: 6 additions & 1 deletion tests/unit/Db/UserMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use OCA\UserOIDC\Db\UserMapper;
use OCA\UserOIDC\Service\LocalIdService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IConfig;
use OCP\IDBConnection;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
Expand All @@ -26,13 +27,17 @@ class UserMapperTest extends TestCase {
/** @var UserMapper|MockObject */
private $userMapper;

/** @var Iconfig|MockObject */
private $config;

public function setUp(): void {
parent::setUp();

$this->config = $this->createMock(IConfig::class);
$this->idService = $this->createMock(LocalIdService::class);
$this->db = $this->createMock(IDBConnection::class);
$this->userMapper = $this->getMockBuilder(UserMapper::class)
->setConstructorArgs([$this->db, $this->idService])
->setConstructorArgs([$this->db, $this->idService, $this->config])
->setMethods(['getUser', 'insert'])
->getMock();
}
Expand Down

0 comments on commit d5119d6

Please sign in to comment.