Skip to content
Draft
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
10 changes: 5 additions & 5 deletions lib/Command/PhoneNumber/AddPhoneNumber.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
use OC\Core\Command\Base;
use OCA\Talk\Model\PhoneNumber;
use OCA\Talk\Model\PhoneNumberMapper;
use OCA\Talk\Service\PhoneNumberValidation;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IPhoneNumberUtil;
use OCP\IUser;
use OCP\IUserManager;
use Symfony\Component\Console\Input\InputArgument;
Expand All @@ -24,7 +24,7 @@ class AddPhoneNumber extends Base {

public function __construct(
private IUserManager $userManager,
private IPhoneNumberUtil $phoneNumberUtil,
private PhoneNumberValidation $phoneNumberValidation,
private PhoneNumberMapper $mapper,
) {
parent::__construct();
Expand Down Expand Up @@ -66,12 +66,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
$userId = $user->getUID();

$phoneNumberStandard = preg_match('/^[0-9]{1,20}$/', $phoneNumber) ? $phoneNumber : $this->phoneNumberUtil->convertToStandardFormat($phoneNumber);
if ($phoneNumberStandard === null) {
try {
$phoneNumber = $this->phoneNumberValidation->validateNumber($phoneNumber);
} catch (\InvalidArgumentException) {
$output->writeln('<error>Not a valid phone number ' . $phoneNumber . '. The format is invalid.</error>');
return self::FAILURE;
}
$phoneNumber = $phoneNumberStandard;

try {
$entry = $this->mapper->findByPhoneNumber($phoneNumber);
Expand Down
10 changes: 5 additions & 5 deletions lib/Command/PhoneNumber/ImportPhoneNumbers.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
use OC\Core\Command\Base;
use OCA\Talk\Model\PhoneNumber;
use OCA\Talk\Model\PhoneNumberMapper;
use OCA\Talk\Service\PhoneNumberValidation;
use OCP\IDBConnection;
use OCP\IPhoneNumberUtil;
use OCP\IUser;
use OCP\IUserManager;
use Symfony\Component\Console\Input\InputInterface;
Expand All @@ -23,7 +23,7 @@ class ImportPhoneNumbers extends Base {

public function __construct(
private IUserManager $userManager,
private IPhoneNumberUtil $phoneNumberUtil,
private PhoneNumberValidation $phoneNumberValidation,
private PhoneNumberMapper $mapper,
private IDBConnection $db,
) {
Expand Down Expand Up @@ -72,12 +72,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
continue;
}

$phoneNumberStandard = preg_match('/^[0-9]{1,20}$/', $row[0]) ? $row[0] : $this->phoneNumberUtil->convertToStandardFormat($row[0]);
if ($phoneNumberStandard === null) {
try {
$row[0] = $this->phoneNumberValidation->validateNumber($row[0]);
} catch (\InvalidArgumentException) {
$output->writeln('<error>Not a valid phone number ' . $row[0] . '. The format is invalid.</error>');
return self::FAILURE;
}
$row[0] = $phoneNumberStandard;

$user = $this->userManager->get($row[1]);
if (!$user instanceof IUser) {
Expand Down
62 changes: 62 additions & 0 deletions lib/Service/PhoneNumberValidation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Talk\Service;

use OCP\IConfig;
use OCP\IPhoneNumberUtil;

class PhoneNumberValidation {

public function __construct(
protected IPhoneNumberUtil $phoneNumberUtil,
protected IConfig $config,
) {
}

/**
* Validate input as a phone number
*
* - Local number: allow
* - International number:
* 1. Replace leading 00 with +
* 2. Check if still valid international number
* a. If valid, strip + and allow
* b. If invalid, throw
* @throws \InvalidArgumentException When the number is invalid
*/
public function validateNumber(string $phoneNumber): string {

if (
// Not an internation number
!str_starts_with($phoneNumber, '00')
// And matches a local number or dial-through
&& preg_match('/^[0-9]{1,20}$/', $phoneNumber)
) {
return $phoneNumber;
Copy link
Member Author

Choose a reason for hiding this comment

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

@fancycode is the free pass for local numbers okay? Or should e.g. 0711 be replaced with 49711 if DE is specified as default phone prefix. I assume it would be bad, as it would prevent specifying 0711… even if that would be what your SIP gate receives

Copy link
Member

Choose a reason for hiding this comment

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

An internal extension should never start with a 0, so you could add the international prefix if configured (i.e. 071112345 becomes +4971112345) or just reject such numbers.

To make things worse, the 00 prefix for international numbers is only if calling from Germany to another country. For example calling from the US to another country needs 011 as prefix, from Australia it's 0011. See https://en.wikipedia.org/wiki/List_of_international_call_prefixes for other prefixes. So replacing 00 with a + will fail for Australian people trying to configure 00114971112345 as German number in Stuttgart.

You could try to guess based on the default phone prefix what the user might try to do, but maybe the easiest is to simply reject numbers that start with a 0.

}

// Replace double leading zero with +
if (str_starts_with($phoneNumber, '00')) {
$phoneNumber = '+' . substr($phoneNumber, 2);
}

$defaultRegion = $this->config->getSystemValueString('default_phone_region') ?: null;
$standardPhoneNumber = $this->phoneNumberUtil->convertToStandardFormat($phoneNumber, $defaultRegion);

if ($standardPhoneNumber === null) {
throw new \InvalidArgumentException();
}

if (str_starts_with($standardPhoneNumber, '+')) {
return substr($standardPhoneNumber, 1);
}

return $standardPhoneNumber;
}
}
28 changes: 28 additions & 0 deletions lib/SetupCheck/SIPConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
namespace OCA\Talk\SetupCheck;

use OCA\Talk\Config;
use OCP\IDBConnection;
use OCP\IL10N;
use OCP\SetupCheck\ISetupCheck;
use OCP\SetupCheck\SetupResult;

class SIPConfiguration implements ISetupCheck {
public function __construct(
protected readonly Config $talkConfig,
protected readonly IDBConnection $connection,
protected readonly IL10N $l,
) {
}
Expand All @@ -39,9 +41,35 @@ public function run(): SetupResult {
if ($this->talkConfig->getSignalingMode() === Config::SIGNALING_INTERNAL) {
return SetupResult::success($this->l->t('Using the SIP functionality requires a High-performance backend.'));
}

$query = $this->connection->getQueryBuilder();
$query->select('phone_number')
->from('talk_phone_numbers')
->where($query->expr()->like('phone_number', $query->createNamedParameter(
$this->connection->escapeLikeParameter('+') . '%'
)))
->orWhere($query->expr()->like('phone_number', $query->createNamedParameter(
$this->connection->escapeLikeParameter('00') . '%'
)));

$result = $query->executeQuery();
$invalidNumbers = $result->fetchFirstColumn();
$result->closeCursor();

if (!empty($invalidNumbers)) {
$message = $this->l->t("Assigned Talk phone numbers must not start with + or 00. Please remove the following numbers:\n{list}");
$message = str_replace(
'{list}',
implode("\n", $invalidNumbers),
$message
);
return SetupResult::error($message, 'https://portal.nextcloud.com/article/Nextcloud-Talk/Nextcloud-Talk-Phone/Direct-Dial-in#content-provisioning');
}

if ($this->talkConfig->getSIPSharedSecret() === '' && $this->talkConfig->getDialInInfo() === '') {
return SetupResult::info($this->l->t('No SIP backend configured'));
}

return SetupResult::success();
}
}
Loading