Merge pull request #24486 from nextcloud/feature/noid/phone-number-validation

Phone number validation and search
This commit is contained in:
Joas Schilling 2020-12-08 17:05:38 +01:00 committed by GitHub
commit 86a3b7e7bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 913 additions and 265 deletions

@ -1 +1 @@
Subproject commit e287243b8eeaf72443b841ace3b1788e85a5aacd
Subproject commit 27a56c5bb9d0ec514a8fb22044fd5f03a51ea2a5

View File

@ -26,6 +26,7 @@
namespace OCA\DAV\CardDAV;
use OC\Accounts\AccountManager;
use OCP\Accounts\IAccountManager;
use OCP\IImage;
use OCP\IUser;
use Sabre\VObject\Component\VCard;
@ -62,8 +63,8 @@ class Converter {
$publish = false;
if ($image !== null && isset($userData[AccountManager::PROPERTY_AVATAR])) {
$userData[AccountManager::PROPERTY_AVATAR]['value'] = true;
if ($image !== null && isset($userData[IAccountManager::PROPERTY_AVATAR])) {
$userData[IAccountManager::PROPERTY_AVATAR]['value'] = true;
}
foreach ($userData as $property => $value) {
@ -76,28 +77,28 @@ class Converter {
if ($shareWithTrustedServers && !$emptyValue) {
$publish = true;
switch ($property) {
case AccountManager::PROPERTY_DISPLAYNAME:
case IAccountManager::PROPERTY_DISPLAYNAME:
$vCard->add(new Text($vCard, 'FN', $value['value']));
$vCard->add(new Text($vCard, 'N', $this->splitFullName($value['value'])));
break;
case AccountManager::PROPERTY_AVATAR:
case IAccountManager::PROPERTY_AVATAR:
if ($image !== null) {
$vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]);
}
break;
case AccountManager::PROPERTY_EMAIL:
case IAccountManager::PROPERTY_EMAIL:
$vCard->add(new Text($vCard, 'EMAIL', $value['value'], ['TYPE' => 'OTHER']));
break;
case AccountManager::PROPERTY_WEBSITE:
case IAccountManager::PROPERTY_WEBSITE:
$vCard->add(new Text($vCard, 'URL', $value['value']));
break;
case AccountManager::PROPERTY_PHONE:
case IAccountManager::PROPERTY_PHONE:
$vCard->add(new Text($vCard, 'TEL', $value['value'], ['TYPE' => 'OTHER']));
break;
case AccountManager::PROPERTY_ADDRESS:
case IAccountManager::PROPERTY_ADDRESS:
$vCard->add(new Text($vCard, 'ADR', $value['value'], ['TYPE' => 'OTHER']));
break;
case AccountManager::PROPERTY_TWITTER:
case IAccountManager::PROPERTY_TWITTER:
$vCard->add(new Text($vCard, 'X-SOCIALPROFILE', $value['value'], ['TYPE' => 'TWITTER']));
break;
}

View File

@ -29,13 +29,14 @@ namespace OCA\DAV\Tests\unit\CardDAV;
use OC\Accounts\AccountManager;
use OCA\DAV\CardDAV\Converter;
use OCP\Accounts\IAccountManager;
use OCP\IImage;
use OCP\IUser;
use Test\TestCase;
class ConverterTest extends TestCase {
/** @var AccountManager | \PHPUnit\Framework\MockObject\MockObject */
/** @var AccountManager|\PHPUnit\Framework\MockObject\MockObject */
private $accountManager;
protected function setUp(): void {
@ -49,36 +50,36 @@ class ConverterTest extends TestCase {
->disableOriginalConstructor()->getMock();
$accountManager->expects($this->any())->method('getUser')->willReturn(
[
AccountManager::PROPERTY_DISPLAYNAME =>
IAccountManager::PROPERTY_DISPLAYNAME =>
[
'value' => $user->getDisplayName(),
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
],
AccountManager::PROPERTY_ADDRESS =>
IAccountManager::PROPERTY_ADDRESS =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
],
AccountManager::PROPERTY_WEBSITE =>
IAccountManager::PROPERTY_WEBSITE =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
],
AccountManager::PROPERTY_EMAIL =>
IAccountManager::PROPERTY_EMAIL =>
[
'value' => $user->getEMailAddress(),
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
],
AccountManager::PROPERTY_AVATAR =>
IAccountManager::PROPERTY_AVATAR =>
[
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY
],
AccountManager::PROPERTY_PHONE =>
IAccountManager::PROPERTY_PHONE =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
],
AccountManager::PROPERTY_TWITTER =>
IAccountManager::PROPERTY_TWITTER =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,

View File

@ -31,6 +31,7 @@ namespace OCA\DAV\Tests\unit\CardDAV;
use OC\Accounts\AccountManager;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\CardDAV\SyncService;
use OCP\Accounts\IAccountManager;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserManager;
@ -132,36 +133,36 @@ class SyncServiceTest extends TestCase {
$accountManager = $this->getMockBuilder(AccountManager::class)->disableOriginalConstructor()->getMock();
$accountManager->expects($this->any())->method('getUser')
->willReturn([
AccountManager::PROPERTY_DISPLAYNAME =>
IAccountManager::PROPERTY_DISPLAYNAME =>
[
'value' => $user->getDisplayName(),
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
],
AccountManager::PROPERTY_ADDRESS =>
IAccountManager::PROPERTY_ADDRESS =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
],
AccountManager::PROPERTY_WEBSITE =>
IAccountManager::PROPERTY_WEBSITE =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
],
AccountManager::PROPERTY_EMAIL =>
IAccountManager::PROPERTY_EMAIL =>
[
'value' => $user->getEMailAddress(),
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
],
AccountManager::PROPERTY_AVATAR =>
IAccountManager::PROPERTY_AVATAR =>
[
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY
],
AccountManager::PROPERTY_PHONE =>
IAccountManager::PROPERTY_PHONE =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
],
AccountManager::PROPERTY_TWITTER =>
IAccountManager::PROPERTY_TWITTER =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,

View File

@ -48,6 +48,7 @@ return [
// Users
['root' => '/cloud', 'name' => 'Users#getUsers', 'url' => '/users', 'verb' => 'GET'],
['root' => '/cloud', 'name' => 'Users#getUsersDetails', 'url' => '/users/details', 'verb' => 'GET'],
['root' => '/cloud', 'name' => 'Users#searchByPhoneNumbers', 'url' => '/users/search/by-phone', 'verb' => 'POST'],
['root' => '/cloud', 'name' => 'Users#addUser', 'url' => '/users', 'verb' => 'POST'],
['root' => '/cloud', 'name' => 'Users#getUser', 'url' => '/users/{userId}', 'verb' => 'GET'],
['root' => '/cloud', 'name' => 'Users#getCurrentUser', 'url' => '/user', 'verb' => 'GET'],

View File

@ -35,6 +35,7 @@ use OC\Accounts\AccountManager;
use OC\User\Backend;
use OC\User\NoUserException;
use OC_Helper;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCS\OCSNotFoundException;
use OCP\AppFramework\OCSController;
@ -135,12 +136,12 @@ abstract class AUserData extends OCSController {
$data['backend'] = $targetUserObject->getBackendClassName();
$data['subadmin'] = $this->getUserSubAdminGroupsData($targetUserObject->getUID());
$data['quota'] = $this->fillStorageInfo($targetUserObject->getUID());
$data[AccountManager::PROPERTY_EMAIL] = $targetUserObject->getEMailAddress();
$data[AccountManager::PROPERTY_DISPLAYNAME] = $targetUserObject->getDisplayName();
$data[AccountManager::PROPERTY_PHONE] = $userAccount[AccountManager::PROPERTY_PHONE]['value'];
$data[AccountManager::PROPERTY_ADDRESS] = $userAccount[AccountManager::PROPERTY_ADDRESS]['value'];
$data[AccountManager::PROPERTY_WEBSITE] = $userAccount[AccountManager::PROPERTY_WEBSITE]['value'];
$data[AccountManager::PROPERTY_TWITTER] = $userAccount[AccountManager::PROPERTY_TWITTER]['value'];
$data[IAccountManager::PROPERTY_EMAIL] = $targetUserObject->getEMailAddress();
$data[IAccountManager::PROPERTY_DISPLAYNAME] = $targetUserObject->getDisplayName();
$data[IAccountManager::PROPERTY_PHONE] = $userAccount[IAccountManager::PROPERTY_PHONE]['value'];
$data[IAccountManager::PROPERTY_ADDRESS] = $userAccount[IAccountManager::PROPERTY_ADDRESS]['value'];
$data[IAccountManager::PROPERTY_WEBSITE] = $userAccount[IAccountManager::PROPERTY_WEBSITE]['value'];
$data[IAccountManager::PROPERTY_TWITTER] = $userAccount[IAccountManager::PROPERTY_TWITTER]['value'];
$data['groups'] = $gids;
$data['language'] = $this->l10nFactory->getUserLanguage($targetUserObject);
$data['locale'] = $this->config->getUserValue($targetUserObject->getUID(), 'core', 'locale');

View File

@ -41,12 +41,18 @@ declare(strict_types=1);
namespace OCA\Provisioning_API\Controller;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberFormat;
use libphonenumber\PhoneNumberUtil;
use OC\Accounts\AccountManager;
use OC\Authentication\Token\RemoteWipe;
use OC\HintException;
use OCA\Provisioning_API\FederatedShareProviderFactory;
use OCA\Settings\Mailer\NewUserMailHelper;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCS\OCSForbiddenException;
@ -55,6 +61,7 @@ use OCP\IGroup;
use OCP\IGroupManager;
use OCP\ILogger;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
@ -67,6 +74,8 @@ class UsersController extends AUserData {
/** @var IAppManager */
private $appManager;
/** @var IURLGenerator */
protected $urlGenerator;
/** @var ILogger */
private $logger;
/** @var IFactory */
@ -90,6 +99,7 @@ class UsersController extends AUserData {
IGroupManager $groupManager,
IUserSession $userSession,
AccountManager $accountManager,
IURLGenerator $urlGenerator,
ILogger $logger,
IFactory $l10nFactory,
NewUserMailHelper $newUserMailHelper,
@ -107,6 +117,7 @@ class UsersController extends AUserData {
$l10nFactory);
$this->appManager = $appManager;
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->l10nFactory = $l10nFactory;
$this->newUserMailHelper = $newUserMailHelper;
@ -201,6 +212,65 @@ class UsersController extends AUserData {
]);
}
/**
* @NoAdminRequired
* @NoSubAdminRequired
*
* @param string $location
* @param array $search
* @return DataResponse
*/
public function searchByPhoneNumbers(string $location, array $search): DataResponse {
$phoneUtil = PhoneNumberUtil::getInstance();
if ($phoneUtil->getCountryCodeForRegion($location) === 0) {
// Not a valid region code
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
$normalizedNumberToKey = [];
foreach ($search as $key => $phoneNumbers) {
foreach ($phoneNumbers as $phone) {
try {
$phoneNumber = $phoneUtil->parse($phone, $location);
if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
$normalizedNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
$normalizedNumberToKey[$normalizedNumber] = (string) $key;
}
} catch (NumberParseException $e) {
}
}
}
$phoneNumbers = array_keys($normalizedNumberToKey);
if (empty($phoneNumbers)) {
return new DataResponse();
}
$userMatches = $this->accountManager->searchUsers(IAccountManager::PROPERTY_PHONE, $phoneNumbers);
if (empty($userMatches)) {
return new DataResponse();
}
$cloudUrl = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
if (strpos($cloudUrl, 'http://') === 0) {
$cloudUrl = substr($cloudUrl, strlen('http://'));
} elseif (strpos($cloudUrl, 'https://') === 0) {
$cloudUrl = substr($cloudUrl, strlen('https://'));
}
$matches = [];
foreach ($userMatches as $phone => $userId) {
// Not using the ICloudIdManager as that would run a search for each contact to find the display name in the address book
$matches[$normalizedNumberToKey[$phone]] = $userId . '@' . $cloudUrl;
}
return new DataResponse($matches);
}
/**
* @throws OCSException
*/
@ -431,17 +501,17 @@ class UsersController extends AUserData {
// Editing self (display, email)
if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
$permittedFields[] = AccountManager::PROPERTY_DISPLAYNAME;
$permittedFields[] = AccountManager::PROPERTY_EMAIL;
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
}
if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
$shareProvider = $this->federatedShareProviderFactory->get();
if ($shareProvider->isLookupServerUploadEnabled()) {
$permittedFields[] = AccountManager::PROPERTY_PHONE;
$permittedFields[] = AccountManager::PROPERTY_ADDRESS;
$permittedFields[] = AccountManager::PROPERTY_WEBSITE;
$permittedFields[] = AccountManager::PROPERTY_TWITTER;
$permittedFields[] = IAccountManager::PROPERTY_PHONE;
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
}
}
@ -474,8 +544,8 @@ class UsersController extends AUserData {
// Editing self (display, email)
if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
$permittedFields[] = 'display';
$permittedFields[] = AccountManager::PROPERTY_DISPLAYNAME;
$permittedFields[] = AccountManager::PROPERTY_EMAIL;
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
}
$permittedFields[] = 'password';
@ -492,10 +562,10 @@ class UsersController extends AUserData {
if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
$shareProvider = $this->federatedShareProviderFactory->get();
if ($shareProvider->isLookupServerUploadEnabled()) {
$permittedFields[] = AccountManager::PROPERTY_PHONE;
$permittedFields[] = AccountManager::PROPERTY_ADDRESS;
$permittedFields[] = AccountManager::PROPERTY_WEBSITE;
$permittedFields[] = AccountManager::PROPERTY_TWITTER;
$permittedFields[] = IAccountManager::PROPERTY_PHONE;
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
}
}
@ -510,15 +580,15 @@ class UsersController extends AUserData {
|| $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
// They have permissions over the user
$permittedFields[] = 'display';
$permittedFields[] = AccountManager::PROPERTY_DISPLAYNAME;
$permittedFields[] = AccountManager::PROPERTY_EMAIL;
$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
$permittedFields[] = 'password';
$permittedFields[] = 'language';
$permittedFields[] = 'locale';
$permittedFields[] = AccountManager::PROPERTY_PHONE;
$permittedFields[] = AccountManager::PROPERTY_ADDRESS;
$permittedFields[] = AccountManager::PROPERTY_WEBSITE;
$permittedFields[] = AccountManager::PROPERTY_TWITTER;
$permittedFields[] = IAccountManager::PROPERTY_PHONE;
$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
$permittedFields[] = 'quota';
} else {
// No rights
@ -532,7 +602,7 @@ class UsersController extends AUserData {
// Process the edit
switch ($key) {
case 'display':
case AccountManager::PROPERTY_DISPLAYNAME:
case IAccountManager::PROPERTY_DISPLAYNAME:
$targetUser->setDisplayName($value);
break;
case 'quota':
@ -577,21 +647,25 @@ class UsersController extends AUserData {
}
$this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value);
break;
case AccountManager::PROPERTY_EMAIL:
case IAccountManager::PROPERTY_EMAIL:
if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') {
$targetUser->setEMailAddress($value);
} else {
throw new OCSException('', 102);
}
break;
case AccountManager::PROPERTY_PHONE:
case AccountManager::PROPERTY_ADDRESS:
case AccountManager::PROPERTY_WEBSITE:
case AccountManager::PROPERTY_TWITTER:
case IAccountManager::PROPERTY_PHONE:
case IAccountManager::PROPERTY_ADDRESS:
case IAccountManager::PROPERTY_WEBSITE:
case IAccountManager::PROPERTY_TWITTER:
$userAccount = $this->accountManager->getUser($targetUser);
if ($userAccount[$key]['value'] !== $value) {
$userAccount[$key]['value'] = $value;
$this->accountManager->updateUser($targetUser, $userAccount);
try {
$this->accountManager->updateUser($targetUser, $userAccount, true);
} catch (\InvalidArgumentException $e) {
throw new OCSException('Invalid ' . $e->getMessage(), 102);
}
}
break;
default:

View File

@ -35,6 +35,7 @@ use OC\Group\Manager;
use OC\SubAdmin;
use OC\User\NoUserException;
use OCA\Provisioning_API\Controller\GroupsController;
use OCP\Accounts\IAccountManager;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IRequest;
@ -185,10 +186,10 @@ class GroupsControllerTest extends \Test\TestCase {
->method('getUser')
->willReturnCallback(function (IUser $user) {
return [
AccountManager::PROPERTY_PHONE => ['value' => '0800-call-' . $user->getUID()],
AccountManager::PROPERTY_ADDRESS => ['value' => 'Holzweg 99, 0601 Herrera, Panama'],
AccountManager::PROPERTY_WEBSITE => ['value' => 'https://' . $user->getUid() . '.pa'],
AccountManager::PROPERTY_TWITTER => ['value' => '@' . $user->getUID()],
IAccountManager::PROPERTY_PHONE => ['value' => '0800-call-' . $user->getUID()],
IAccountManager::PROPERTY_ADDRESS => ['value' => 'Holzweg 99, 0601 Herrera, Panama'],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://' . $user->getUid() . '.pa'],
IAccountManager::PROPERTY_TWITTER => ['value' => '@' . $user->getUID()],
];
});
}

View File

@ -48,6 +48,7 @@ use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Provisioning_API\Controller\UsersController;
use OCA\Provisioning_API\FederatedShareProviderFactory;
use OCA\Settings\Mailer\NewUserMailHelper;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use OCP\AppFramework\Http\DataResponse;
use OCP\EventDispatcher\IEventDispatcher;
@ -56,6 +57,7 @@ use OCP\IGroup;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
@ -85,6 +87,8 @@ class UsersControllerTest extends TestCase {
protected $api;
/** @var AccountManager|MockObject */
protected $accountManager;
/** @var IURLGenerator|MockObject */
protected $urlGenerator;
/** @var IRequest|MockObject */
protected $request;
/** @var IFactory|MockObject */
@ -111,6 +115,7 @@ class UsersControllerTest extends TestCase {
$this->logger = $this->createMock(ILogger::class);
$this->request = $this->createMock(IRequest::class);
$this->accountManager = $this->createMock(AccountManager::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->l10nFactory = $this->createMock(IFactory::class);
$this->newUserMailHelper = $this->createMock(NewUserMailHelper::class);
$this->federatedShareProviderFactory = $this->createMock(FederatedShareProviderFactory::class);
@ -128,6 +133,7 @@ class UsersControllerTest extends TestCase {
$this->groupManager,
$this->userSession,
$this->accountManager,
$this->urlGenerator,
$this->logger,
$this->l10nFactory,
$this->newUserMailHelper,
@ -381,7 +387,7 @@ class UsersControllerTest extends TestCase {
}
public function testAddUserSuccessfulWithDisplayName() {
$api = $this->getMockBuilder('OCA\Provisioning_API\Controller\UsersController')
$api = $this->getMockBuilder(UsersController::class)
->setConstructorArgs([
'provisioning_api',
$this->request,
@ -391,6 +397,7 @@ class UsersControllerTest extends TestCase {
$this->groupManager,
$this->userSession,
$this->accountManager,
$this->urlGenerator,
$this->logger,
$this->l10nFactory,
$this->newUserMailHelper,
@ -993,10 +1000,10 @@ class UsersControllerTest extends TestCase {
->with($targetUser)
->willReturn(
[
AccountManager::PROPERTY_ADDRESS => ['value' => 'address'],
AccountManager::PROPERTY_PHONE => ['value' => 'phone'],
AccountManager::PROPERTY_TWITTER => ['value' => 'twitter'],
AccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
IAccountManager::PROPERTY_ADDRESS => ['value' => 'address'],
IAccountManager::PROPERTY_PHONE => ['value' => 'phone'],
IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
]
);
$this->config
@ -1162,10 +1169,10 @@ class UsersControllerTest extends TestCase {
->with($targetUser)
->willReturn(
[
AccountManager::PROPERTY_ADDRESS => ['value' => 'address'],
AccountManager::PROPERTY_PHONE => ['value' => 'phone'],
AccountManager::PROPERTY_TWITTER => ['value' => 'twitter'],
AccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
IAccountManager::PROPERTY_ADDRESS => ['value' => 'address'],
IAccountManager::PROPERTY_PHONE => ['value' => 'phone'],
IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
]
);
@ -1333,10 +1340,10 @@ class UsersControllerTest extends TestCase {
->with($targetUser)
->willReturn(
[
AccountManager::PROPERTY_ADDRESS => ['value' => 'address'],
AccountManager::PROPERTY_PHONE => ['value' => 'phone'],
AccountManager::PROPERTY_TWITTER => ['value' => 'twitter'],
AccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
IAccountManager::PROPERTY_ADDRESS => ['value' => 'address'],
IAccountManager::PROPERTY_PHONE => ['value' => 'phone'],
IAccountManager::PROPERTY_TWITTER => ['value' => 'twitter'],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'website'],
]
);
@ -1370,6 +1377,47 @@ class UsersControllerTest extends TestCase {
$this->assertEquals($expected, $this->invokePrivate($this->api, 'getUserData', ['UID']));
}
public function dataSearchByPhoneNumbers(): array {
return [
'Invalid country' => ['Not a country code', ['12345' => ['NaN']], 400, null, null, []],
'No number to search' => ['DE', ['12345' => ['NaN']], 200, null, null, []],
'Valid number but no match' => ['DE', ['12345' => ['0711 / 25 24 28-90']], 200, ['+4971125242890'], [], []],
'Invalid number' => ['FR', ['12345' => ['0711 / 25 24 28-90']], 200, null, null, []],
'Invalid and valid number' => ['DE', ['12345' => ['NaN', '0711 / 25 24 28-90']], 200, ['+4971125242890'], [], []],
'Valid and invalid number' => ['DE', ['12345' => ['0711 / 25 24 28-90', 'NaN']], 200, ['+4971125242890'], [], []],
'Valid number and a match' => ['DE', ['12345' => ['0711 / 25 24 28-90']], 200, ['+4971125242890'], ['+4971125242890' => 'admin'], ['12345' => 'admin@localhost']],
'Same number twice, later hits' => ['DE', ['12345' => ['0711 / 25 24 28-90'], '23456' => ['0711 / 25 24 28-90']], 200, ['+4971125242890'], ['+4971125242890' => 'admin'], ['23456' => 'admin@localhost']],
];
}
/**
* @dataProvider dataSearchByPhoneNumbers
* @param string $location
* @param array $search
* @param int $status
* @param array $expected
*/
public function testSearchByPhoneNumbers(string $location, array $search, int $status, ?array $searchUsers, ?array $userMatches, array $expected) {
if ($searchUsers === null) {
$this->accountManager->expects($this->never())
->method('searchUsers');
} else {
$this->accountManager->expects($this->once())
->method('searchUsers')
->with(IAccountManager::PROPERTY_PHONE, $searchUsers)
->willReturn($userMatches);
}
$this->urlGenerator->method('getAbsoluteURL')
->with('/')
->willReturn('https://localhost/');
$response = $this->api->searchByPhoneNumbers($location, $search);
self::assertEquals($status, $response->getStatus());
self::assertEquals($expected, $response->getData());
}
public function testEditUserRegularUserSelfEditChangeDisplayName() {
$loggedInUser = $this->getMockBuilder(IUser::class)
->disableOriginalConstructor()
@ -3162,7 +3210,7 @@ class UsersControllerTest extends TestCase {
->willReturn($user);
/** @var UsersController | MockObject $api */
$api = $this->getMockBuilder('OCA\Provisioning_API\Controller\UsersController')
$api = $this->getMockBuilder(UsersController::class)
->setConstructorArgs([
'provisioning_api',
$this->request,
@ -3172,6 +3220,7 @@ class UsersControllerTest extends TestCase {
$this->groupManager,
$this->userSession,
$this->accountManager,
$this->urlGenerator,
$this->logger,
$this->l10nFactory,
$this->newUserMailHelper,
@ -3227,7 +3276,7 @@ class UsersControllerTest extends TestCase {
public function testGetUser() {
/** @var UsersController | MockObject $api */
$api = $this->getMockBuilder('OCA\Provisioning_API\Controller\UsersController')
$api = $this->getMockBuilder(UsersController::class)
->setConstructorArgs([
'provisioning_api',
$this->request,
@ -3237,6 +3286,7 @@ class UsersControllerTest extends TestCase {
$this->groupManager,
$this->userSession,
$this->accountManager,
$this->urlGenerator,
$this->logger,
$this->l10nFactory,
$this->newUserMailHelper,
@ -3566,22 +3616,22 @@ class UsersControllerTest extends TestCase {
return [
[false, false, []],
[false, true, [
AccountManager::PROPERTY_PHONE,
AccountManager::PROPERTY_ADDRESS,
AccountManager::PROPERTY_WEBSITE,
AccountManager::PROPERTY_TWITTER,
IAccountManager::PROPERTY_PHONE,
IAccountManager::PROPERTY_ADDRESS,
IAccountManager::PROPERTY_WEBSITE,
IAccountManager::PROPERTY_TWITTER,
]],
[ true, false, [
AccountManager::PROPERTY_DISPLAYNAME,
AccountManager::PROPERTY_EMAIL,
IAccountManager::PROPERTY_DISPLAYNAME,
IAccountManager::PROPERTY_EMAIL,
]],
[ true, true ,[
AccountManager::PROPERTY_DISPLAYNAME,
AccountManager::PROPERTY_EMAIL,
AccountManager::PROPERTY_PHONE,
AccountManager::PROPERTY_ADDRESS,
AccountManager::PROPERTY_WEBSITE,
AccountManager::PROPERTY_TWITTER,
IAccountManager::PROPERTY_DISPLAYNAME,
IAccountManager::PROPERTY_EMAIL,
IAccountManager::PROPERTY_PHONE,
IAccountManager::PROPERTY_ADDRESS,
IAccountManager::PROPERTY_WEBSITE,
IAccountManager::PROPERTY_TWITTER,
]]
];
}

View File

@ -24,6 +24,11 @@
if (_.isUndefined(data)) {
return null;
}
if (data.status && data.status === 'error') {
OC.Notification.show(data.data.message, { type: 'error' });
}
if (_.isUndefined(data.data)) {
return null;
}
@ -47,4 +52,4 @@
OC.Settings = OC.Settings || {};
OC.Settings.UserSettings = UserSettings;
})();
})();

View File

@ -30,6 +30,7 @@
namespace OCA\Settings\BackgroundJobs;
use OC\Accounts\AccountManager;
use OCP\Accounts\IAccountManager;
use OCP\AppFramework\Http;
use OCP\BackgroundJob\IJobList;
use OCP\BackgroundJob\Job;
@ -114,11 +115,11 @@ class VerifyUserData extends Job {
$try = (int)$argument['try'] + 1;
switch ($argument['type']) {
case AccountManager::PROPERTY_WEBSITE:
case IAccountManager::PROPERTY_WEBSITE:
$result = $this->verifyWebsite($argument);
break;
case AccountManager::PROPERTY_TWITTER:
case AccountManager::PROPERTY_EMAIL:
case IAccountManager::PROPERTY_TWITTER:
case IAccountManager::PROPERTY_EMAIL:
$result = $this->verifyViaLookupServer($argument, $argument['type']);
break;
default:
@ -164,9 +165,9 @@ class VerifyUserData extends Job {
$userData = $this->accountManager->getUser($user);
if ($publishedCodeSanitized === $argument['verificationCode']) {
$userData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFIED;
$userData[IAccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFIED;
} else {
$userData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::NOT_VERIFIED;
$userData[IAccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::NOT_VERIFIED;
}
$this->accountManager->updateUser($user, $userData);

View File

@ -752,6 +752,7 @@ Raw output
PhpOutputBuffering::class => ['pass' => $phpOutputBuffering->run(), 'description' => $phpOutputBuffering->description(), 'severity' => $phpOutputBuffering->severity()],
LegacySSEKeyFormat::class => ['pass' => $legacySSEKeyFormat->run(), 'description' => $legacySSEKeyFormat->description(), 'severity' => $legacySSEKeyFormat->severity(), 'linkToDocumentation' => $legacySSEKeyFormat->linkToDocumentation()],
CheckUserCertificates::class => ['pass' => $checkUserCertificates->run(), 'description' => $checkUserCertificates->description(), 'severity' => $checkUserCertificates->severity(), 'elements' => $checkUserCertificates->elements()],
'isDefaultPhoneRegionSet' => $this->config->getSystemValueString('default_phone_region', '') !== '',
]
);
}

View File

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
@ -30,7 +32,6 @@
// FIXME: disabled for now to be able to inject IGroupManager and also use
// getSubAdmin()
//declare(strict_types=1);
namespace OCA\Settings\Controller;
@ -46,6 +47,7 @@ use OCA\FederatedFileSharing\FederatedShareProvider;
use OCA\Settings\BackgroundJobs\VerifyUserData;
use OCA\Settings\Events\BeforeTemplateRenderedEvent;
use OCA\User_LDAP\User_Proxy;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
@ -140,7 +142,7 @@ class UsersController extends Controller {
*
* @return TemplateResponse
*/
public function usersListByGroup() {
public function usersListByGroup(): TemplateResponse {
return $this->usersList();
}
@ -152,7 +154,7 @@ class UsersController extends Controller {
*
* @return TemplateResponse
*/
public function usersList() {
public function usersList(): TemplateResponse {
$user = $this->userSession->getUser();
$uid = $user->getUID();
@ -309,7 +311,7 @@ class UsersController extends Controller {
*
* @return bool
*/
protected function canAdminChangeUserPasswords() {
protected function canAdminChangeUserPasswords(): bool {
$isEncryptionEnabled = $this->encryptionManager->isEnabled();
try {
$noUserSpecificEncryptionKeys = !$this->encryptionManager->getEncryptionModule()->needDetailedAccessList();
@ -344,19 +346,19 @@ class UsersController extends Controller {
* @param string $twitterScope
* @return DataResponse
*/
public function setUserSettings($avatarScope,
$displayname,
$displaynameScope,
$phone,
$phoneScope,
$email,
$emailScope,
$website,
$websiteScope,
$address,
$addressScope,
$twitter,
$twitterScope
public function setUserSettings(string $avatarScope,
string $displayname,
string $displaynameScope,
string $phone,
string $phoneScope,
string $email,
string $emailScope,
string $website,
string $websiteScope,
string $address,
string $addressScope,
string $twitter,
string $twitterScope
) {
$email = strtolower($email);
if (!empty($email) && !$this->mailer->validateMailAddress($email)) {
@ -372,36 +374,40 @@ class UsersController extends Controller {
}
$user = $this->userSession->getUser();
$data = $this->accountManager->getUser($user);
$data[AccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
$data[IAccountManager::PROPERTY_AVATAR] = ['scope' => $avatarScope];
if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
$data[AccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
$data[AccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
$data[IAccountManager::PROPERTY_DISPLAYNAME] = ['value' => $displayname, 'scope' => $displaynameScope];
$data[IAccountManager::PROPERTY_EMAIL] = ['value' => $email, 'scope' => $emailScope];
}
if ($this->appManager->isEnabledForUser('federatedfilesharing')) {
$shareProvider = \OC::$server->query(FederatedShareProvider::class);
if ($shareProvider->isLookupServerUploadEnabled()) {
$data[AccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
$data[AccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
$data[AccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
$data[AccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
$data[IAccountManager::PROPERTY_WEBSITE] = ['value' => $website, 'scope' => $websiteScope];
$data[IAccountManager::PROPERTY_ADDRESS] = ['value' => $address, 'scope' => $addressScope];
$data[IAccountManager::PROPERTY_PHONE] = ['value' => $phone, 'scope' => $phoneScope];
$data[IAccountManager::PROPERTY_TWITTER] = ['value' => $twitter, 'scope' => $twitterScope];
}
}
try {
$this->saveUserSettings($user, $data);
$data = $this->saveUserSettings($user, $data);
return new DataResponse(
[
'status' => 'success',
'data' => [
'userId' => $user->getUID(),
'avatarScope' => $data[AccountManager::PROPERTY_AVATAR]['scope'],
'displayname' => $data[AccountManager::PROPERTY_DISPLAYNAME]['value'],
'displaynameScope' => $data[AccountManager::PROPERTY_DISPLAYNAME]['scope'],
'email' => $data[AccountManager::PROPERTY_EMAIL]['value'],
'emailScope' => $data[AccountManager::PROPERTY_EMAIL]['scope'],
'website' => $data[AccountManager::PROPERTY_WEBSITE]['value'],
'websiteScope' => $data[AccountManager::PROPERTY_WEBSITE]['scope'],
'address' => $data[AccountManager::PROPERTY_ADDRESS]['value'],
'addressScope' => $data[AccountManager::PROPERTY_ADDRESS]['scope'],
'avatarScope' => $data[IAccountManager::PROPERTY_AVATAR]['scope'],
'displayname' => $data[IAccountManager::PROPERTY_DISPLAYNAME]['value'],
'displaynameScope' => $data[IAccountManager::PROPERTY_DISPLAYNAME]['scope'],
'phone' => $data[IAccountManager::PROPERTY_PHONE]['value'],
'phoneScope' => $data[IAccountManager::PROPERTY_PHONE]['scope'],
'email' => $data[IAccountManager::PROPERTY_EMAIL]['value'],
'emailScope' => $data[IAccountManager::PROPERTY_EMAIL]['scope'],
'website' => $data[IAccountManager::PROPERTY_WEBSITE]['value'],
'websiteScope' => $data[IAccountManager::PROPERTY_WEBSITE]['scope'],
'address' => $data[IAccountManager::PROPERTY_ADDRESS]['value'],
'addressScope' => $data[IAccountManager::PROPERTY_ADDRESS]['scope'],
'twitter' => $data[IAccountManager::PROPERTY_TWITTER]['value'],
'twitterScope' => $data[IAccountManager::PROPERTY_TWITTER]['scope'],
'message' => $this->l10n->t('Settings saved')
]
],
@ -414,6 +420,13 @@ class UsersController extends Controller {
'message' => $e->getMessage()
],
]);
} catch (\InvalidArgumentException $e) {
return new DataResponse([
'status' => 'error',
'data' => [
'message' => $e->getMessage()
],
]);
}
}
/**
@ -421,34 +434,45 @@ class UsersController extends Controller {
*
* @param IUser $user
* @param array $data
* @return array
* @throws ForbiddenException
* @throws \InvalidArgumentException
*/
protected function saveUserSettings(IUser $user, array $data) {
protected function saveUserSettings(IUser $user, array $data): array {
// keep the user back-end up-to-date with the latest display name and email
// address
$oldDisplayName = $user->getDisplayName();
$oldDisplayName = is_null($oldDisplayName) ? '' : $oldDisplayName;
if (isset($data[AccountManager::PROPERTY_DISPLAYNAME]['value'])
&& $oldDisplayName !== $data[AccountManager::PROPERTY_DISPLAYNAME]['value']
if (isset($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
&& $oldDisplayName !== $data[IAccountManager::PROPERTY_DISPLAYNAME]['value']
) {
$result = $user->setDisplayName($data[AccountManager::PROPERTY_DISPLAYNAME]['value']);
$result = $user->setDisplayName($data[IAccountManager::PROPERTY_DISPLAYNAME]['value']);
if ($result === false) {
throw new ForbiddenException($this->l10n->t('Unable to change full name'));
}
}
$oldEmailAddress = $user->getEMailAddress();
$oldEmailAddress = is_null($oldEmailAddress) ? '' : strtolower($oldEmailAddress);
if (isset($data[AccountManager::PROPERTY_EMAIL]['value'])
&& $oldEmailAddress !== $data[AccountManager::PROPERTY_EMAIL]['value']
if (isset($data[IAccountManager::PROPERTY_EMAIL]['value'])
&& $oldEmailAddress !== $data[IAccountManager::PROPERTY_EMAIL]['value']
) {
// this is the only permission a backend provides and is also used
// for the permission of setting a email address
if (!$user->canChangeDisplayName()) {
throw new ForbiddenException($this->l10n->t('Unable to change email address'));
}
$user->setEMailAddress($data[AccountManager::PROPERTY_EMAIL]['value']);
$user->setEMailAddress($data[IAccountManager::PROPERTY_EMAIL]['value']);
}
try {
return $this->accountManager->updateUser($user, $data, true);
} catch (\InvalidArgumentException $e) {
if ($e->getMessage() === IAccountManager::PROPERTY_PHONE) {
throw new \InvalidArgumentException($this->l10n->t('Unable to set invalid phone number'));
}
throw new \InvalidArgumentException($this->l10n->t('Some account data was invalid'));
}
$this->accountManager->updateUser($user, $data);
}
/**
@ -479,26 +503,25 @@ class UsersController extends Controller {
switch ($account) {
case 'verify-twitter':
$accountData[AccountManager::PROPERTY_TWITTER]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
$accountData[IAccountManager::PROPERTY_TWITTER]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
$msg = $this->l10n->t('In order to verify your Twitter account, post the following tweet on Twitter (please make sure to post it without any line breaks):');
$code = $codeMd5;
$type = AccountManager::PROPERTY_TWITTER;
$data = $accountData[AccountManager::PROPERTY_TWITTER]['value'];
$accountData[AccountManager::PROPERTY_TWITTER]['signature'] = $signature;
$type = IAccountManager::PROPERTY_TWITTER;
$accountData[IAccountManager::PROPERTY_TWITTER]['signature'] = $signature;
break;
case 'verify-website':
$accountData[AccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
$accountData[IAccountManager::PROPERTY_WEBSITE]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
$msg = $this->l10n->t('In order to verify your Website, store the following content in your web-root at \'.well-known/CloudIdVerificationCode.txt\' (please make sure that the complete text is in one line):');
$type = AccountManager::PROPERTY_WEBSITE;
$data = $accountData[AccountManager::PROPERTY_WEBSITE]['value'];
$accountData[AccountManager::PROPERTY_WEBSITE]['signature'] = $signature;
$type = IAccountManager::PROPERTY_WEBSITE;
$accountData[IAccountManager::PROPERTY_WEBSITE]['signature'] = $signature;
break;
default:
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
if ($onlyVerificationCode === false) {
$this->accountManager->updateUser($user, $accountData);
$accountData = $this->accountManager->updateUser($user, $accountData);
$data = $accountData[$type]['value'];
$this->jobList->add(VerifyUserData::class,
[

View File

@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
@ -33,6 +35,7 @@ namespace OCA\Settings\Settings\Personal;
use OC\Accounts\AccountManager;
use OCA\FederatedFileSharing\FederatedShareProvider;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Files\FileInfo;
@ -62,14 +65,6 @@ class PersonalInfo implements ISettings {
/** @var IL10N */
private $l;
/**
* @param IConfig $config
* @param IUserManager $userManager
* @param IGroupManager $groupManager
* @param AccountManager $accountManager
* @param IFactory $l10nFactory
* @param IL10N $l
*/
public function __construct(
IConfig $config,
IUserManager $userManager,
@ -88,11 +83,7 @@ class PersonalInfo implements ISettings {
$this->l = $l;
}
/**
* @return TemplateResponse returns the instance with all parameters set, ready to be rendered
* @since 9.1
*/
public function getForm() {
public function getForm(): TemplateResponse {
$federatedFileSharingEnabled = $this->appManager->isEnabledForUser('federatedfilesharing');
$lookupServerUploadEnabled = false;
if ($federatedFileSharingEnabled) {
@ -126,23 +117,23 @@ class PersonalInfo implements ISettings {
'quota' => $storageInfo['quota'],
'avatarChangeSupported' => $user->canChangeAvatar(),
'lookupServerUploadEnabled' => $lookupServerUploadEnabled,
'avatarScope' => $userData[AccountManager::PROPERTY_AVATAR]['scope'],
'avatarScope' => $userData[IAccountManager::PROPERTY_AVATAR]['scope'],
'displayNameChangeSupported' => $user->canChangeDisplayName(),
'displayName' => $userData[AccountManager::PROPERTY_DISPLAYNAME]['value'],
'displayNameScope' => $userData[AccountManager::PROPERTY_DISPLAYNAME]['scope'],
'email' => $userData[AccountManager::PROPERTY_EMAIL]['value'],
'emailScope' => $userData[AccountManager::PROPERTY_EMAIL]['scope'],
'emailVerification' => $userData[AccountManager::PROPERTY_EMAIL]['verified'],
'phone' => $userData[AccountManager::PROPERTY_PHONE]['value'],
'phoneScope' => $userData[AccountManager::PROPERTY_PHONE]['scope'],
'address' => $userData[AccountManager::PROPERTY_ADDRESS]['value'],
'addressScope' => $userData[AccountManager::PROPERTY_ADDRESS]['scope'],
'website' => $userData[AccountManager::PROPERTY_WEBSITE]['value'],
'websiteScope' => $userData[AccountManager::PROPERTY_WEBSITE]['scope'],
'websiteVerification' => $userData[AccountManager::PROPERTY_WEBSITE]['verified'],
'twitter' => $userData[AccountManager::PROPERTY_TWITTER]['value'],
'twitterScope' => $userData[AccountManager::PROPERTY_TWITTER]['scope'],
'twitterVerification' => $userData[AccountManager::PROPERTY_TWITTER]['verified'],
'displayName' => $userData[IAccountManager::PROPERTY_DISPLAYNAME]['value'],
'displayNameScope' => $userData[IAccountManager::PROPERTY_DISPLAYNAME]['scope'],
'email' => $userData[IAccountManager::PROPERTY_EMAIL]['value'],
'emailScope' => $userData[IAccountManager::PROPERTY_EMAIL]['scope'],
'emailVerification' => $userData[IAccountManager::PROPERTY_EMAIL]['verified'],
'phone' => $userData[IAccountManager::PROPERTY_PHONE]['value'],
'phoneScope' => $userData[IAccountManager::PROPERTY_PHONE]['scope'],
'address' => $userData[IAccountManager::PROPERTY_ADDRESS]['value'],
'addressScope' => $userData[IAccountManager::PROPERTY_ADDRESS]['scope'],
'website' => $userData[IAccountManager::PROPERTY_WEBSITE]['value'],
'websiteScope' => $userData[IAccountManager::PROPERTY_WEBSITE]['scope'],
'websiteVerification' => $userData[IAccountManager::PROPERTY_WEBSITE]['verified'],
'twitter' => $userData[IAccountManager::PROPERTY_TWITTER]['value'],
'twitterScope' => $userData[IAccountManager::PROPERTY_TWITTER]['scope'],
'twitterVerification' => $userData[IAccountManager::PROPERTY_TWITTER]['verified'],
'groups' => $this->getGroups($user),
] + $messageParameters + $languageParameters + $localeParameters;
@ -154,7 +145,7 @@ class PersonalInfo implements ISettings {
* @return string the section ID, e.g. 'sharing'
* @since 9.1
*/
public function getSection() {
public function getSection(): string {
return 'personal-info';
}
@ -166,7 +157,7 @@ class PersonalInfo implements ISettings {
* E.g.: 70
* @since 9.1
*/
public function getPriority() {
public function getPriority(): int {
return 10;
}
@ -176,9 +167,9 @@ class PersonalInfo implements ISettings {
* @param IUser $user
* @return array
*/
private function getGroups(IUser $user) {
private function getGroups(IUser $user): array {
$groups = array_map(
function (IGroup $group) {
static function (IGroup $group) {
return $group->getDisplayName();
},
$this->groupManager->getUserGroups($user)
@ -195,7 +186,7 @@ class PersonalInfo implements ISettings {
* @param IUser $user
* @return array
*/
private function getLanguages(IUser $user) {
private function getLanguages(IUser $user): array {
$forceLanguage = $this->config->getSystemValue('force_language', false);
if ($forceLanguage !== false) {
return [];
@ -228,7 +219,7 @@ class PersonalInfo implements ISettings {
);
}
private function getLocales(IUser $user) {
private function getLocales(IUser $user): array {
$forceLanguage = $this->config->getSystemValue('force_locale', false);
if ($forceLanguage !== false) {
return [];
@ -273,8 +264,8 @@ class PersonalInfo implements ISettings {
* @param array $userData
* @return array
*/
private function getMessageParameters(array $userData) {
$needVerifyMessage = [AccountManager::PROPERTY_EMAIL, AccountManager::PROPERTY_WEBSITE, AccountManager::PROPERTY_TWITTER];
private function getMessageParameters(array $userData): array {
$needVerifyMessage = [IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_WEBSITE, IAccountManager::PROPERTY_TWITTER];
$messageParameters = [];
foreach ($needVerifyMessage as $property) {
switch ($userData[$property]['verified']) {

View File

@ -67,6 +67,7 @@ script('settings', [
</div>
</div>
<span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="avatarscope" value="<?php p($_['avatarScope']) ?>">
<?php } ?>
@ -161,7 +162,7 @@ script('settings', [
} ?>
placeholder="<?php p($l->t('Your email address')); ?>"
autocomplete="on" autocapitalize="none" autocorrect="off" />
<span class="icon-checkmark hidden"></span>
<span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span>
<?php if (!$_['displayNameChangeSupported']) { ?>
<span><?php if (isset($_['email']) && !empty($_['email'])) {
@ -196,6 +197,7 @@ script('settings', [
placeholder="<?php p($l->t('Your phone number')); ?>"
autocomplete="on" autocapitalize="none" autocorrect="off" />
<span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="phonescope" value="<?php p($_['phoneScope']) ?>">
<?php } ?>
@ -220,6 +222,7 @@ script('settings', [
value="<?php p($_['address']) ?>"
autocomplete="on" autocapitalize="none" autocorrect="off" />
<span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="addressscope" value="<?php p($_['addressScope']) ?>">
<?php } ?>
@ -275,6 +278,7 @@ script('settings', [
} ?>
/>
<span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="websitescope" value="<?php p($_['websiteScope']) ?>">
<?php } ?>
@ -330,6 +334,7 @@ script('settings', [
} ?>
/>
<span class="icon-checkmark hidden"></span>
<span class="icon-error hidden" ></span>
<?php if ($_['lookupServerUploadEnabled']) { ?>
<input type="hidden" id="twitterscope" value="<?php p($_['twitterScope']) ?>">
<?php } ?>

View File

@ -605,6 +605,7 @@ class CheckSetupControllerTest extends TestCase {
'OCA\Settings\SetupChecks\LegacySSEKeyFormat' => ['pass' => true, 'description' => 'The old server-side-encryption format is enabled. We recommend disabling this.', 'severity' => 'warning', 'linkToDocumentation' => ''],
'OCA\Settings\SetupChecks\CheckUserCertificates' => ['pass' => false, 'description' => 'There are some user imported SSL certificates present, that are not used anymore with Nextcloud 21. They can be imported on the command line via "occ security:certificates:import" command. Their paths inside the data directory are shown below.', 'severity' => 'warning', 'elements' => ['a', 'b']],
'imageMagickLacksSVGSupport' => false,
'isDefaultPhoneRegionSet' => false,
]
);
$this->assertEquals($expected, $this->checkSetupController->check());

View File

@ -31,6 +31,7 @@ use OC\Accounts\AccountManager;
use OC\Encryption\Exceptions\ModuleDoesNotExistsException;
use OC\Group\Manager;
use OCA\Settings\Controller\UsersController;
use OCP\Accounts\IAccountManager;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\BackgroundJob\IJobList;
@ -196,41 +197,41 @@ class UsersControllerTest extends \Test\TestCase {
->method('getUser')
->with($user)
->willReturn([
AccountManager::PROPERTY_DISPLAYNAME =>
IAccountManager::PROPERTY_DISPLAYNAME =>
[
'value' => 'Display name',
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
'verified' => AccountManager::NOT_VERIFIED,
],
AccountManager::PROPERTY_ADDRESS =>
IAccountManager::PROPERTY_ADDRESS =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
'verified' => AccountManager::NOT_VERIFIED,
],
AccountManager::PROPERTY_WEBSITE =>
IAccountManager::PROPERTY_WEBSITE =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
'verified' => AccountManager::NOT_VERIFIED,
],
AccountManager::PROPERTY_EMAIL =>
IAccountManager::PROPERTY_EMAIL =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
'verified' => AccountManager::NOT_VERIFIED,
],
AccountManager::PROPERTY_AVATAR =>
IAccountManager::PROPERTY_AVATAR =>
[
'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY
],
AccountManager::PROPERTY_PHONE =>
IAccountManager::PROPERTY_PHONE =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
'verified' => AccountManager::NOT_VERIFIED,
],
AccountManager::PROPERTY_TWITTER =>
IAccountManager::PROPERTY_TWITTER =>
[
'value' => '',
'scope' => AccountManager::VISIBILITY_PRIVATE,
@ -238,12 +239,14 @@ class UsersControllerTest extends \Test\TestCase {
],
]);
$controller->expects($this->once())->method('saveUserSettings');
$controller->expects($this->once())
->method('saveUserSettings')
->willReturnArgument(1);
} else {
$controller->expects($this->never())->method('saveUserSettings');
}
$result = $controller->setUserSettings(
$result = $controller->setUserSettings(//
AccountManager::VISIBILITY_CONTACTS_ONLY,
'displayName',
AccountManager::VISIBILITY_CONTACTS_ONLY,
@ -289,21 +292,21 @@ class UsersControllerTest extends \Test\TestCase {
$user->method('getEMailAddress')->willReturn($oldEmailAddress);
$user->method('canChangeDisplayName')->willReturn(true);
if ($data[AccountManager::PROPERTY_EMAIL]['value'] === $oldEmailAddress ||
($oldEmailAddress === null && $data[AccountManager::PROPERTY_EMAIL]['value'] === '')) {
if ($data[IAccountManager::PROPERTY_EMAIL]['value'] === $oldEmailAddress ||
($oldEmailAddress === null && $data[IAccountManager::PROPERTY_EMAIL]['value'] === '')) {
$user->expects($this->never())->method('setEMailAddress');
} else {
$user->expects($this->once())->method('setEMailAddress')
->with($data[AccountManager::PROPERTY_EMAIL]['value'])
->with($data[IAccountManager::PROPERTY_EMAIL]['value'])
->willReturn(true);
}
if ($data[AccountManager::PROPERTY_DISPLAYNAME]['value'] === $oldDisplayName ||
($oldDisplayName === null && $data[AccountManager::PROPERTY_DISPLAYNAME]['value'] === '')) {
if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] === $oldDisplayName ||
($oldDisplayName === null && $data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] === '')) {
$user->expects($this->never())->method('setDisplayName');
} else {
$user->expects($this->once())->method('setDisplayName')
->with($data[AccountManager::PROPERTY_DISPLAYNAME]['value'])
->with($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
->willReturn(true);
}
@ -317,48 +320,48 @@ class UsersControllerTest extends \Test\TestCase {
return [
[
[
AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
],
'john@example.com',
'john doe'
],
[
[
AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
],
'johnNew@example.com',
'john New doe'
],
[
[
AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
],
'johnNew@example.com',
'john doe'
],
[
[
AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
],
'john@example.com',
'john New doe'
],
[
[
AccountManager::PROPERTY_EMAIL => ['value' => ''],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
IAccountManager::PROPERTY_EMAIL => ['value' => ''],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
],
null,
'john New doe'
],
[
[
AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
],
'john@example.com',
null
@ -391,14 +394,14 @@ class UsersControllerTest extends \Test\TestCase {
$user->method('getDisplayName')->willReturn($oldDisplayName);
$user->method('getEMailAddress')->willReturn($oldEmailAddress);
if ($data[AccountManager::PROPERTY_EMAIL]['value'] !== $oldEmailAddress) {
if ($data[IAccountManager::PROPERTY_EMAIL]['value'] !== $oldEmailAddress) {
$user->method('canChangeDisplayName')
->willReturn($canChangeEmail);
}
if ($data[AccountManager::PROPERTY_DISPLAYNAME]['value'] !== $oldDisplayName) {
if ($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'] !== $oldDisplayName) {
$user->method('setDisplayName')
->with($data[AccountManager::PROPERTY_DISPLAYNAME]['value'])
->with($data[IAccountManager::PROPERTY_DISPLAYNAME]['value'])
->willReturn($setDisplayNameResult);
}
@ -410,8 +413,8 @@ class UsersControllerTest extends \Test\TestCase {
return [
[
[
AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
],
'johnNew@example.com',
'john New doe',
@ -420,8 +423,8 @@ class UsersControllerTest extends \Test\TestCase {
],
[
[
AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
],
'johnNew@example.com',
'john New doe',
@ -430,8 +433,8 @@ class UsersControllerTest extends \Test\TestCase {
],
[
[
AccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
IAccountManager::PROPERTY_EMAIL => ['value' => 'john@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'john doe'],
],
'johnNew@example.com',
'john New doe',
@ -455,7 +458,7 @@ class UsersControllerTest extends \Test\TestCase {
$signature = 'theSignature';
$code = $message . ' ' . $signature;
if ($type === AccountManager::PROPERTY_TWITTER) {
if ($type === IAccountManager::PROPERTY_TWITTER) {
$code = $message . ' ' . md5($signature);
}
@ -470,7 +473,7 @@ class UsersControllerTest extends \Test\TestCase {
$controller->expects($this->any())->method('getCurrentTime')->willReturn(1234567);
if ($onlyVerificationCode === false) {
$this->accountManager->expects($this->once())->method('updateUser')->with($user, $expectedData);
$this->accountManager->expects($this->once())->method('updateUser')->with($user, $expectedData)->willReturnArgument(1);
$this->jobList->expects($this->once())->method('add')
->with('OCA\Settings\BackgroundJobs\VerifyUserData',
[
@ -492,25 +495,25 @@ class UsersControllerTest extends \Test\TestCase {
public function dataTestGetVerificationCode() {
$accountDataBefore = [
AccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => AccountManager::NOT_VERIFIED],
AccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => AccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => AccountManager::NOT_VERIFIED],
IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => AccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
];
$accountDataAfterWebsite = [
AccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => AccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
AccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => AccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => AccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => AccountManager::NOT_VERIFIED, 'signature' => 'theSignature'],
];
$accountDataAfterTwitter = [
AccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => AccountManager::NOT_VERIFIED],
AccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => AccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
IAccountManager::PROPERTY_WEBSITE => ['value' => 'https://nextcloud.com', 'verified' => AccountManager::NOT_VERIFIED],
IAccountManager::PROPERTY_TWITTER => ['value' => '@nextclouders', 'verified' => AccountManager::VERIFICATION_IN_PROGRESS, 'signature' => 'theSignature'],
];
return [
['verify-twitter', AccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, false],
['verify-website', AccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, false],
['verify-twitter', AccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, true],
['verify-website', AccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, true],
['verify-twitter', IAccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, false],
['verify-website', IAccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, false],
['verify-twitter', IAccountManager::PROPERTY_TWITTER, $accountDataBefore, $accountDataAfterTwitter, true],
['verify-website', IAccountManager::PROPERTY_WEBSITE, $accountDataBefore, $accountDataAfterWebsite, true],
];
}

View File

@ -173,6 +173,37 @@ trait Provisioning {
}
}
/**
* @Then /^search users by phone for region "([^"]*)" with$/
*
* @param string $user
* @param \Behat\Gherkin\Node\TableNode|null $settings
*/
public function searchUserByPhone($region, \Behat\Gherkin\Node\TableNode $searchTable) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php/cloud/users/search/by-phone";
$client = new Client();
$options = [];
$options['auth'] = $this->adminUser;
$options['headers'] = [
'OCS-APIREQUEST' => 'true',
];
$search = [];
foreach ($searchTable->getRows() as $row) {
if (!isset($search[$row[0]])) {
$search[$row[0]] = [];
}
$search[$row[0]][] = $row[1];
}
$options['form_params'] = [
'location' => $region,
'search' => $search,
];
$this->response = $client->post($fullUrl, $options);
}
public function createUser($user) {
$previous_user = $this->currentUser;
$this->currentUser = "admin";
@ -560,6 +591,19 @@ trait Provisioning {
}
}
/**
* @Then /^phone matches returned are$/
* @param \Behat\Gherkin\Node\TableNode|null $usersList
*/
public function thePhoneUsersShouldBe($usersList) {
if ($usersList instanceof \Behat\Gherkin\Node\TableNode) {
$users = $usersList->getRowsHash();
$listCheckedElements = simplexml_load_string($this->response->getBody())->data;
$respondedArray = json_decode(json_encode($listCheckedElements), true);
Assert::assertEquals($users, $respondedArray);
}
}
/**
* @Then /^detailed users returned are$/
* @param \Behat\Gherkin\Node\TableNode|null $usersList

View File

@ -71,12 +71,12 @@ Feature: provisioning
And the HTTP status code should be "200"
And sending "PUT" to "/cloud/users/brand-new-user" with
| key | email |
| value | brand-new-user@gmail.com |
| value | no-reply@nextcloud.com |
And the OCS status code should be "100"
And the HTTP status code should be "200"
And sending "PUT" to "/cloud/users/brand-new-user" with
| key | phone |
| value | 0123 456 789 |
| value | +49 711 / 25 24 28-90 |
And the OCS status code should be "100"
And the HTTP status code should be "200"
And sending "PUT" to "/cloud/users/brand-new-user" with
@ -97,12 +97,29 @@ Feature: provisioning
Then user "brand-new-user" has
| id | brand-new-user |
| displayname | Brand New User |
| email | brand-new-user@gmail.com |
| phone | 0123 456 789 |
| email | no-reply@nextcloud.com |
| phone | +4971125242890 |
| address | Foo Bar Town |
| website | https://nextcloud.com |
| twitter | Nextcloud |
Scenario: Search by phone number
Given As an "admin"
And user "phone-user" exists
And sending "PUT" to "/cloud/users/phone-user" with
| key | phone |
| value | +49 711 / 25 24 28-90 |
And the OCS status code should be "100"
And the HTTP status code should be "200"
Then search users by phone for region "DE" with
| random-string1 | 0711 / 123 456 78 |
| random-string1 | 0711 / 252 428-90 |
| random-string2 | 0711 / 90-824 252 |
And the OCS status code should be "100"
And the HTTP status code should be "200"
Then phone matches returned are
| random-string1 | phone-user@localhost:8080 |
Scenario: Create a group
Given As an "admin"
And group "new-group" does not exist

View File

@ -194,6 +194,16 @@ $CONFIG = [
*/
'default_locale' => 'en_US',
/**
* This sets the default region for phone numbers on your Nextcloud server,
* using ISO 3166-1 country codes such as ``DE`` for Germany, ``FR`` for France,
* It is required to allow inserting phone numbers in the user profiles starting
* without the country code (e.g. +49 for Germany).
*
* No default value!
*/
'default_phone_region' => 'EN',
/**
* With this setting a locale can be forced for all users. If a locale is
* forced, the users are also unable to change their locale in the personal

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Core\Migrations;
use Closure;
use Doctrine\DBAL\Types\Types;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version21000Date20201202095923 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('accounts_data')) {
$table = $schema->createTable('accounts_data');
$table->addColumn('id', Types::BIGINT, [
'autoincrement' => true,
'notnull' => true,
'length' => 20,
]);
$table->addColumn('uid', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('name', Types::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('value', Types::STRING, [
'notnull' => false,
'length' => 255,
'default' => '',
]);
$table->setPrimaryKey(['id']);
$table->addIndex(['uid'], 'accounts_data_uid');
$table->addIndex(['name'], 'accounts_data_name');
$table->addIndex(['value'], 'accounts_data_value');
return $schema;
}
return null;
}
}

View File

@ -216,6 +216,12 @@
type: OC.SetupChecks.MESSAGE_TYPE_WARNING
});
}
if (!data.isDefaultPhoneRegionSet) {
messages.push({
msg: t('core', 'Your installation has no default phone region set. This is required to be able to validate phone numbers in the profile settings without a country code. To allow numbers without a country code, please add "default_phone_region" with the respective ISO 3166-1 code of the wished region.'),
type: OC.SetupChecks.MESSAGE_TYPE_INFO
});
}
if (data.cronErrors.length > 0) {
var listOfCronErrors = "";
data.cronErrors.forEach(function(element){

View File

@ -251,6 +251,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -306,6 +307,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -362,6 +364,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -416,6 +419,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -468,6 +472,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -522,6 +527,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -574,6 +580,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -626,6 +633,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -678,6 +686,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -751,6 +760,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -804,6 +814,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -857,6 +868,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -910,6 +922,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -962,6 +975,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: true,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
@ -1014,6 +1028,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyDocs: 'https://docs.nextcloud.com/foo/bar.html',
reverseProxyGeneratedURL: 'http://server',
@ -1067,6 +1082,7 @@ describe('OC.SetupChecks tests', function() {
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: true,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: false,
reverseProxyGeneratedURL: 'https://server',
})
@ -1080,6 +1096,59 @@ describe('OC.SetupChecks tests', function() {
done();
});
});
it('should return an info if there is no default phone region', function(done) {
var async = OC.SetupChecks.checkSetup();
suite.server.requests[0].respond(
200,
{
'Content-Type': 'application/json',
},
JSON.stringify({
hasFileinfoInstalled: true,
isGetenvServerWorking: true,
isReadOnlyConfig: false,
hasWorkingFileLocking: true,
hasValidTransactionIsolationLevel: true,
suggestedOverwriteCliURL: '',
isRandomnessSecure: true,
securityDocs: 'https://docs.owncloud.org/myDocs.html',
serverHasInternetConnectionProblems: false,
isMemcacheConfigured: true,
forwardedForHeadersWorking: true,
isCorrectMemcachedPHPModuleInstalled: true,
hasPassedCodeIntegrityCheck: true,
isOpcacheProperlySetup: true,
hasOpcacheLoaded: true,
isSettimelimitAvailable: true,
hasFreeTypeSupport: true,
missingIndexes: [],
missingPrimaryKeys: [],
missingColumns: [],
cronErrors: [],
cronInfo: {
diffInSeconds: 0
},
isMemoryLimitSufficient: true,
appDirsWithDifferentOwner: [],
recommendedPHPModules: [],
pendingBigIntConversionColumns: [],
isMysqlUsedWithoutUTF8MB4: false,
isDefaultPhoneRegionSet: false,
isEnoughTempSpaceAvailableIfS3PrimaryStorageIsUsed: true,
reverseProxyGeneratedURL: 'https://server',
})
);
async.done(function( data, s, x ){
expect(data).toEqual([{
msg: 'Your installation has no default phone region set. This is required to be able to validate phone numbers in the profile settings without a country code. To allow numbers without a country code, please add "default_phone_region" with the respective ISO 3166-1 code of the wished region.',
type: OC.SetupChecks.MESSAGE_TYPE_INFO
}]);
done();
});
});
});
describe('checkGeneric', function() {

View File

@ -930,6 +930,7 @@ return array(
'OC\\Core\\Migrations\\Version20000Date20201109081918' => $baseDir . '/core/Migrations/Version20000Date20201109081918.php',
'OC\\Core\\Migrations\\Version20000Date20201109081919' => $baseDir . '/core/Migrations/Version20000Date20201109081919.php',
'OC\\Core\\Migrations\\Version20000Date20201111081915' => $baseDir . '/core/Migrations/Version20000Date20201111081915.php',
'OC\\Core\\Migrations\\Version21000Date20201202095923' => $baseDir . '/core/Migrations/Version21000Date20201202095923.php',
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php',
@ -1266,6 +1267,7 @@ return array(
'OC\\Repair\\NC20\\EncryptionMigration' => $baseDir . '/lib/private/Repair/NC20/EncryptionMigration.php',
'OC\\Repair\\NC20\\ShippedDashboardEnable' => $baseDir . '/lib/private/Repair/NC20/ShippedDashboardEnable.php',
'OC\\Repair\\NC21\\AddCheckForUserCertificatesJob' => $baseDir . '/lib/private/Repair/NC21/AddCheckForUserCertificatesJob.php',
'OC\\Repair\\NC21\\ValidatePhoneNumber' => $baseDir . '/lib/private/Repair/NC21/ValidatePhoneNumber.php',
'OC\\Repair\\OldGroupMembershipShares' => $baseDir . '/lib/private/Repair/OldGroupMembershipShares.php',
'OC\\Repair\\Owncloud\\DropAccountTermsTable' => $baseDir . '/lib/private/Repair/Owncloud/DropAccountTermsTable.php',
'OC\\Repair\\Owncloud\\SaveAccountsTableData' => $baseDir . '/lib/private/Repair/Owncloud/SaveAccountsTableData.php',

View File

@ -959,6 +959,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Migrations\\Version20000Date20201109081918' => __DIR__ . '/../../..' . '/core/Migrations/Version20000Date20201109081918.php',
'OC\\Core\\Migrations\\Version20000Date20201109081919' => __DIR__ . '/../../..' . '/core/Migrations/Version20000Date20201109081919.php',
'OC\\Core\\Migrations\\Version20000Date20201111081915' => __DIR__ . '/../../..' . '/core/Migrations/Version20000Date20201111081915.php',
'OC\\Core\\Migrations\\Version21000Date20201202095923' => __DIR__ . '/../../..' . '/core/Migrations/Version21000Date20201202095923.php',
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php',
@ -1295,6 +1296,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Repair\\NC20\\EncryptionMigration' => __DIR__ . '/../../..' . '/lib/private/Repair/NC20/EncryptionMigration.php',
'OC\\Repair\\NC20\\ShippedDashboardEnable' => __DIR__ . '/../../..' . '/lib/private/Repair/NC20/ShippedDashboardEnable.php',
'OC\\Repair\\NC21\\AddCheckForUserCertificatesJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC21/AddCheckForUserCertificatesJob.php',
'OC\\Repair\\NC21\\ValidatePhoneNumber' => __DIR__ . '/../../..' . '/lib/private/Repair/NC21/ValidatePhoneNumber.php',
'OC\\Repair\\OldGroupMembershipShares' => __DIR__ . '/../../..' . '/lib/private/Repair/OldGroupMembershipShares.php',
'OC\\Repair\\Owncloud\\DropAccountTermsTable' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/DropAccountTermsTable.php',
'OC\\Repair\\Owncloud\\SaveAccountsTableData' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/SaveAccountsTableData.php',

View File

@ -30,10 +30,16 @@
namespace OC\Accounts;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberFormat;
use libphonenumber\PhoneNumberUtil;
use OCA\Settings\BackgroundJobs\VerifyUserData;
use OCP\Accounts\IAccount;
use OCP\Accounts\IAccountManager;
use OCP\BackgroundJob\IJobList;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUser;
use Psr\Log\LoggerInterface;
@ -55,9 +61,15 @@ class AccountManager implements IAccountManager {
/** @var IDBConnection database connection */
private $connection;
/** @var IConfig */
private $config;
/** @var string table name */
private $table = 'accounts';
/** @var string table name */
private $dataTable = 'accounts_data';
/** @var EventDispatcherInterface */
private $eventDispatcher;
@ -68,24 +80,70 @@ class AccountManager implements IAccountManager {
private $logger;
public function __construct(IDBConnection $connection,
IConfig $config,
EventDispatcherInterface $eventDispatcher,
IJobList $jobList,
LoggerInterface $logger) {
$this->connection = $connection;
$this->config = $config;
$this->eventDispatcher = $eventDispatcher;
$this->jobList = $jobList;
$this->logger = $logger;
}
/**
* @param string $input
* @return string Provided phone number in E.164 format when it was a valid number
* @throws \InvalidArgumentException When the phone number was invalid or no default region is set and the number doesn't start with a country code
*/
protected function parsePhoneNumber(string $input): string {
$defaultRegion = $this->config->getSystemValueString('default_phone_region', '');
if ($defaultRegion === '') {
// When no default region is set, only +49… numbers are valid
if (strpos($input, '+') !== 0) {
throw new \InvalidArgumentException(self::PROPERTY_PHONE);
}
$defaultRegion = 'EN';
}
$phoneUtil = PhoneNumberUtil::getInstance();
try {
$phoneNumber = $phoneUtil->parse($input, $defaultRegion);
if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
return $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
}
} catch (NumberParseException $e) {
}
throw new \InvalidArgumentException(self::PROPERTY_PHONE);
}
/**
* update user record
*
* @param IUser $user
* @param $data
* @param array $data
* @param bool $throwOnData Set to true if you can inform the user about invalid data
* @return array The potentially modified data (e.g. phone numbers are converted to E.164 format)
* @throws \InvalidArgumentException Message is the property that was invalid
*/
public function updateUser(IUser $user, $data) {
public function updateUser(IUser $user, array $data, bool $throwOnData = false): array {
$userData = $this->getUser($user);
$updated = true;
if (isset($data[self::PROPERTY_PHONE]) && $data[self::PROPERTY_PHONE]['value'] !== '') {
try {
$data[self::PROPERTY_PHONE]['value'] = $this->parsePhoneNumber($data[self::PROPERTY_PHONE]['value']);
} catch (\InvalidArgumentException $e) {
if ($throwOnData) {
throw $e;
}
$data[self::PROPERTY_PHONE]['value'] = '';
}
}
if (empty($userData)) {
$this->insertNewUser($user, $data);
} elseif ($userData !== $data) {
@ -103,6 +161,8 @@ class AccountManager implements IAccountManager {
new GenericEvent($user, $data)
);
}
return $data;
}
/**
@ -116,6 +176,21 @@ class AccountManager implements IAccountManager {
$query->delete($this->table)
->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
->execute();
$this->deleteUserData($user);
}
/**
* delete user from accounts table
*
* @param IUser $user
*/
public function deleteUserData(IUser $user): void {
$uid = $user->getUID();
$query = $this->connection->getQueryBuilder();
$query->delete($this->dataTable)
->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
->execute();
}
/**
@ -153,6 +228,24 @@ class AccountManager implements IAccountManager {
return $userDataArray;
}
public function searchUsers(string $property, array $values): array {
$query = $this->connection->getQueryBuilder();
$query->select('*')
->from($this->dataTable)
->where($query->expr()->eq('name', $query->createNamedParameter($property)))
->andWhere($query->expr()->in('value', $query->createNamedParameter($values, IQueryBuilder::PARAM_STR_ARRAY)));
$result = $query->execute();
$matches = [];
while ($row = $result->fetch()) {
$matches[$row['value']] = $row['uid'];
}
$result->closeCursor();
return $matches;
}
/**
* check if we need to ask the server for email verification, if yes we create a cronjob
*
@ -173,7 +266,7 @@ class AccountManager implements IAccountManager {
'lastRun' => time()
]
);
$newData[AccountManager::PROPERTY_EMAIL]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
$newData[self::PROPERTY_EMAIL]['verified'] = self::VERIFICATION_IN_PROGRESS;
}
return $newData;
@ -256,7 +349,7 @@ class AccountManager implements IAccountManager {
* @param IUser $user
* @param array $data
*/
protected function insertNewUser(IUser $user, $data) {
protected function insertNewUser(IUser $user, array $data): void {
$uid = $user->getUID();
$jsonEncodedData = json_encode($data);
$query = $this->connection->getQueryBuilder();
@ -268,6 +361,9 @@ class AccountManager implements IAccountManager {
]
)
->execute();
$this->deleteUserData($user);
$this->writeUserData($user, $data);
}
/**
@ -276,7 +372,7 @@ class AccountManager implements IAccountManager {
* @param IUser $user
* @param array $data
*/
protected function updateExistingUser(IUser $user, $data) {
protected function updateExistingUser(IUser $user, array $data): void {
$uid = $user->getUID();
$jsonEncodedData = json_encode($data);
$query = $this->connection->getQueryBuilder();
@ -284,6 +380,30 @@ class AccountManager implements IAccountManager {
->set('data', $query->createNamedParameter($jsonEncodedData))
->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
->execute();
$this->deleteUserData($user);
$this->writeUserData($user, $data);
}
protected function writeUserData(IUser $user, array $data): void {
$query = $this->connection->getQueryBuilder();
$query->insert($this->dataTable)
->values(
[
'uid' => $query->createNamedParameter($user->getUID()),
'name' => $query->createParameter('name'),
'value' => $query->createParameter('value'),
]
);
foreach ($data as $propertyName => $property) {
if ($propertyName === self::PROPERTY_AVATAR) {
continue;
}
$query->setParameter('name', $propertyName)
->setParameter('value', $property['value']);
$query->execute();
}
}
/**

View File

@ -24,6 +24,7 @@
namespace OC\Accounts;
use OCP\Accounts\IAccountManager;
use OCP\IUser;
use Psr\Log\LoggerInterface;
@ -61,14 +62,14 @@ class Hooks {
switch ($feature) {
case 'eMailAddress':
if ($accountData[AccountManager::PROPERTY_EMAIL]['value'] !== $newValue) {
$accountData[AccountManager::PROPERTY_EMAIL]['value'] = $newValue;
if ($accountData[IAccountManager::PROPERTY_EMAIL]['value'] !== $newValue) {
$accountData[IAccountManager::PROPERTY_EMAIL]['value'] = $newValue;
$accountManager->updateUser($user, $accountData);
}
break;
case 'displayName':
if ($accountData[AccountManager::PROPERTY_DISPLAYNAME]['value'] !== $newValue) {
$accountData[AccountManager::PROPERTY_DISPLAYNAME]['value'] = $newValue;
if ($accountData[IAccountManager::PROPERTY_DISPLAYNAME]['value'] !== $newValue) {
$accountData[IAccountManager::PROPERTY_DISPLAYNAME]['value'] = $newValue;
$accountManager->updateUser($user, $accountData);
}
break;

View File

@ -53,6 +53,7 @@ use OC\Repair\NC20\EncryptionLegacyCipher;
use OC\Repair\NC20\EncryptionMigration;
use OC\Repair\NC20\ShippedDashboardEnable;
use OC\Repair\NC21\AddCheckForUserCertificatesJob;
use OC\Repair\NC21\ValidatePhoneNumber;
use OC\Repair\OldGroupMembershipShares;
use OC\Repair\Owncloud\DropAccountTermsTable;
use OC\Repair\Owncloud\SaveAccountsTableData;
@ -177,7 +178,8 @@ class Repair implements IOutput {
*/
public static function getExpensiveRepairSteps() {
return [
new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager())
new OldGroupMembershipShares(\OC::$server->getDatabaseConnection(), \OC::$server->getGroupManager()),
\OC::$server->get(ValidatePhoneNumber::class),
];
}

View File

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
namespace OC\Repair\NC21;
use OC\Accounts\AccountManager;
use OCP\Accounts\IAccountManager;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class ValidatePhoneNumber implements IRepairStep {
/** @var IConfig */
protected $config;
/** @var IUserManager */
protected $userManager;
/** @var AccountManager */
private $accountManager;
public function __construct(IUserManager $userManager,
AccountManager $accountManager,
IConfig $config) {
$this->config = $config;
$this->userManager = $userManager;
$this->accountManager = $accountManager;
}
public function getName(): string {
return 'Validate the phone number and store it in a known format for search';
}
private function shouldRun(): bool {
return true;
}
public function run(IOutput $output): void {
if ($this->config->getSystemValueString('default_phone_region', '') === '') {
throw new \Exception('Can not validate phone numbers without `default_phone_region` being set in the config file');
}
$numUpdated = 0;
$numRemoved = 0;
$this->userManager->callForSeenUsers(function (IUser $user) use (&$numUpdated, &$numRemoved) {
$account = $this->accountManager->getUser($user);
if ($account[IAccountManager::PROPERTY_PHONE]['value'] !== '') {
$updated = $this->accountManager->updateUser($user, $account);
if ($account[IAccountManager::PROPERTY_PHONE]['value'] !== $updated[IAccountManager::PROPERTY_PHONE]['value']) {
if ($updated[IAccountManager::PROPERTY_PHONE]['value'] === '') {
$numRemoved++;
} else {
$numUpdated++;
}
}
}
});
if ($numRemoved > 0 || $numUpdated > 0) {
$output->info('Updated ' . $numUpdated . ' entries and cleaned ' . $numRemoved . ' invalid phone numbers');
}
}
}

View File

@ -65,4 +65,15 @@ interface IAccountManager {
* @return IAccount
*/
public function getAccount(IUser $user): IAccount;
/**
* Search for users based on account data
*
* @param string $property
* @param string[] $values
* @return array
*
* @since 21.0.0
*/
public function searchUsers(string $property, array $values): array;
}

View File

@ -25,6 +25,7 @@ use OC\Accounts\Account;
use OC\Accounts\AccountManager;
use OCP\Accounts\IAccountManager;
use OCP\BackgroundJob\IJobList;
use OCP\IConfig;
use OCP\IUser;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
@ -43,6 +44,9 @@ class AccountsManagerTest extends TestCase {
/** @var \OCP\IDBConnection */
private $connection;
/** @var IConfig|MockObject */
private $config;
/** @var EventDispatcherInterface|MockObject */
private $eventDispatcher;
@ -59,6 +63,7 @@ class AccountsManagerTest extends TestCase {
parent::setUp();
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$this->connection = \OC::$server->getDatabaseConnection();
$this->config = $this->createMock(IConfig::class);
$this->jobList = $this->createMock(IJobList::class);
$this->logger = $this->createMock(LoggerInterface::class);
}
@ -77,7 +82,13 @@ class AccountsManagerTest extends TestCase {
*/
public function getInstance($mockedMethods = null) {
return $this->getMockBuilder(AccountManager::class)
->setConstructorArgs([$this->connection, $this->eventDispatcher, $this->jobList, $this->logger])
->setConstructorArgs([
$this->connection,
$this->config,
$this->eventDispatcher,
$this->jobList,
$this->logger,
])
->setMethods($mockedMethods)
->getMock();
}
@ -187,9 +198,9 @@ class AccountsManagerTest extends TestCase {
public function testUpdateExistingUser() {
$user = $this->getMockBuilder(IUser::class)->getMock();
$user->expects($this->once())->method('getUID')->willReturn('uid');
$oldData = ['key' => 'value'];
$newData = ['newKey' => 'newValue'];
$user->expects($this->atLeastOnce())->method('getUID')->willReturn('uid');
$oldData = ['key' => ['value' => 'value']];
$newData = ['newKey' => ['value' => 'newValue']];
$accountManager = $this->getInstance();
$this->addDummyValuesToTable('uid', $oldData);
@ -201,10 +212,10 @@ class AccountsManagerTest extends TestCase {
public function testInsertNewUser() {
$user = $this->getMockBuilder(IUser::class)->getMock();
$uid = 'uid';
$data = ['key' => 'value'];
$data = ['key' => ['value' => 'value']];
$accountManager = $this->getInstance();
$user->expects($this->once())->method('getUID')->willReturn($uid);
$user->expects($this->atLeastOnce())->method('getUID')->willReturn($uid);
$this->assertNull($this->getDataFromTable($uid));
$this->invokePrivate($accountManager, 'insertNewUser', [$user, $data]);
@ -293,4 +304,32 @@ class AccountsManagerTest extends TestCase {
->willReturn($data);
$this->assertEquals($expected, $accountManager->getAccount($user));
}
public function dataParsePhoneNumber(): array {
return [
['0711 / 25 24 28-90', 'DE', '+4971125242890'],
['0711 / 25 24 28-90', '', null],
['+49 711 / 25 24 28-90', '', '+4971125242890'],
];
}
/**
* @dataProvider dataParsePhoneNumber
* @param string $phoneInput
* @param string $defaultRegion
* @param string|null $phoneNumber
*/
public function testParsePhoneNumber(string $phoneInput, string $defaultRegion, ?string $phoneNumber): void {
$this->config->method('getSystemValueString')
->willReturn($defaultRegion);
$instance = $this->getInstance();
if ($phoneNumber === null) {
$this->expectException(\InvalidArgumentException::class);
self::invokePrivate($instance, 'parsePhoneNumber', [$phoneInput]);
} else {
self::assertEquals($phoneNumber, self::invokePrivate($instance, 'parsePhoneNumber', [$phoneInput]));
}
}
}

View File

@ -23,6 +23,7 @@ namespace Test\Accounts;
use OC\Accounts\AccountManager;
use OC\Accounts\Hooks;
use OCP\Accounts\IAccountManager;
use OCP\IUser;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
@ -77,11 +78,11 @@ class HooksTest extends TestCase {
$this->accountManager->expects($this->once())->method('getUser')->willReturn($data);
$newData = $data;
if ($setEmail) {
$newData[AccountManager::PROPERTY_EMAIL]['value'] = $params['value'];
$newData[IAccountManager::PROPERTY_EMAIL]['value'] = $params['value'];
$this->accountManager->expects($this->once())->method('updateUser')
->with($params['user'], $newData);
} elseif ($setDisplayName) {
$newData[AccountManager::PROPERTY_DISPLAYNAME]['value'] = $params['value'];
$newData[IAccountManager::PROPERTY_DISPLAYNAME]['value'] = $params['value'];
$this->accountManager->expects($this->once())->method('updateUser')
->with($params['user'], $newData);
} else {
@ -98,48 +99,48 @@ class HooksTest extends TestCase {
[
['feature' => '', 'value' => ''],
[
AccountManager::PROPERTY_EMAIL => ['value' => ''],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => '']
IAccountManager::PROPERTY_EMAIL => ['value' => ''],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => '']
],
false, false, true
],
[
['user' => $user, 'value' => ''],
[
AccountManager::PROPERTY_EMAIL => ['value' => ''],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => '']
IAccountManager::PROPERTY_EMAIL => ['value' => ''],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => '']
],
false, false, true
],
[
['user' => $user, 'feature' => ''],
[
AccountManager::PROPERTY_EMAIL => ['value' => ''],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => '']
IAccountManager::PROPERTY_EMAIL => ['value' => ''],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => '']
],
false, false, true
],
[
['user' => $user, 'feature' => 'foo', 'value' => 'bar'],
[
AccountManager::PROPERTY_EMAIL => ['value' => 'oldMail@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'oldDisplayName']
IAccountManager::PROPERTY_EMAIL => ['value' => 'oldMail@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'oldDisplayName']
],
false, false, false
],
[
['user' => $user, 'feature' => 'eMailAddress', 'value' => 'newMail@example.com'],
[
AccountManager::PROPERTY_EMAIL => ['value' => 'oldMail@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'oldDisplayName']
IAccountManager::PROPERTY_EMAIL => ['value' => 'oldMail@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'oldDisplayName']
],
true, false, false
],
[
['user' => $user, 'feature' => 'displayName', 'value' => 'newDisplayName'],
[
AccountManager::PROPERTY_EMAIL => ['value' => 'oldMail@example.com'],
AccountManager::PROPERTY_DISPLAYNAME => ['value' => 'oldDisplayName']
IAccountManager::PROPERTY_EMAIL => ['value' => 'oldMail@example.com'],
IAccountManager::PROPERTY_DISPLAYNAME => ['value' => 'oldDisplayName']
],
false, true, false
],

View File

@ -29,7 +29,7 @@
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
$OC_Version = [21, 0, 0, 7];
$OC_Version = [21, 0, 0, 8];
// The human readable string
$OC_VersionString = '21.0.0 alpha';