summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--apps/dav/lib/AppInfo/Application.php6
-rw-r--r--apps/dav/lib/CardDAV/Converter.php144
-rw-r--r--apps/dav/lib/CardDAV/SyncService.php27
-rw-r--r--apps/dav/lib/HookManager.php9
-rw-r--r--apps/dav/tests/unit/CardDAV/ConverterTest.php138
-rw-r--r--apps/dav/tests/unit/CardDAV/SyncServiceTest.php50
-rw-r--r--apps/dav/tests/unit/DAV/HookManagerTest.php13
-rw-r--r--apps/federatedfilesharing/lib/FederatedShareProvider.php10
-rw-r--r--apps/federatedfilesharing/lib/Settings/Admin.php1
-rw-r--r--apps/federatedfilesharing/templates/settings-admin.php7
-rw-r--r--apps/federatedfilesharing/tests/Settings/AdminTest.php5
-rw-r--r--apps/files/css/files.css20
-rw-r--r--apps/files_sharing/lib/Controller/ShareesAPIController.php58
-rw-r--r--apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php24
-rw-r--r--apps/lookup_server_connector/appinfo/app.php46
-rw-r--r--apps/lookup_server_connector/appinfo/info.xml18
-rw-r--r--apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php81
-rw-r--r--apps/lookup_server_connector/lib/UpdateLookupServer.php136
-rw-r--r--build/integration/features/provisioning-v1.feature1
-rw-r--r--core/Application.php11
-rw-r--r--core/Controller/OCSController.php32
-rw-r--r--core/css/apps.css25
-rw-r--r--core/css/header.css8
-rw-r--r--core/js/sharedialogview.js4
-rw-r--r--core/js/tests/specs/sharedialogviewSpec.js18
-rw-r--r--core/routes.php1
-rw-r--r--core/shipped.json2
-rw-r--r--db_structure.xml32
-rw-r--r--lib/private/Accounts/AccountManager.php201
-rw-r--r--lib/private/Security/IdentityProof/Key.php46
-rw-r--r--lib/private/Security/IdentityProof/Manager.php108
-rw-r--r--lib/private/Security/IdentityProof/Signer.php120
-rw-r--r--lib/private/URLGenerator.php5
-rw-r--r--lib/private/Updater.php2
-rw-r--r--settings/Application.php136
-rw-r--r--settings/Controller/UsersController.php179
-rw-r--r--settings/css/settings.css91
-rw-r--r--settings/js/federationscopemenu.js156
-rw-r--r--settings/js/federationsettingsview.js176
-rw-r--r--settings/js/personal.js27
-rw-r--r--settings/js/usersettings.js50
-rw-r--r--settings/personal.php29
-rw-r--r--settings/routes.php2
-rw-r--r--settings/templates/personal.php182
-rw-r--r--tests/Core/Controller/OCSControllerTest.php49
-rw-r--r--tests/Settings/Controller/UsersControllerTest.php82
-rw-r--r--tests/lib/Accounts/AccountsManagerTest.php202
-rw-r--r--tests/lib/App/ManagerTest.php4
-rw-r--r--tests/lib/AppTest.php9
-rw-r--r--tests/lib/Security/IdentityProof/ManagerTest.php166
-rw-r--r--version.php3
52 files changed, 2527 insertions, 426 deletions
diff --git a/.gitignore b/.gitignore
index 964701eea63..fa49588fad4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@
!/apps/files_sharing
!/apps/files_trashbin
!/apps/files_versions
+!/apps/lookup_server_connector
!/apps/user_ldap
!/apps/provisioning_api
!/apps/systemtags
diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php
index 844e0780ffb..7c32fda8f5a 100644
--- a/apps/dav/lib/AppInfo/Application.php
+++ b/apps/dav/lib/AppInfo/Application.php
@@ -101,6 +101,12 @@ class Application extends App {
}
});
+ $dispatcher->addListener('OC\AccountManager::userUpdated', function(GenericEvent $event) {
+ $user = $event->getSubject();
+ $syncService = $this->getContainer()->query(SyncService::class);
+ $syncService->updateUser($user);
+ });
+
$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', function(GenericEvent $event) {
/** @var Backend $backend */
$backend = $this->getContainer()->query(Backend::class);
diff --git a/apps/dav/lib/CardDAV/Converter.php b/apps/dav/lib/CardDAV/Converter.php
index d1fb754017e..065c5494ec3 100644
--- a/apps/dav/lib/CardDAV/Converter.php
+++ b/apps/dav/lib/CardDAV/Converter.php
@@ -22,6 +22,7 @@
namespace OCA\DAV\CardDAV;
+use OC\Accounts\AccountManager;
use OCP\IImage;
use OCP\IUser;
use Sabre\VObject\Component\VCard;
@@ -29,109 +30,76 @@ use Sabre\VObject\Property\Text;
class Converter {
+ /** @var AccountManager */
+ private $accountManager;
+
/**
- * @param IUser $user
- * @return VCard
+ * Converter constructor.
+ *
+ * @param AccountManager $accountManager
*/
- public function createCardFromUser(IUser $user) {
-
- $uid = $user->getUID();
- $displayName = $user->getDisplayName();
- $displayName = empty($displayName ) ? $uid : $displayName;
- $emailAddress = $user->getEMailAddress();
- $cloudId = $user->getCloudId();
- $image = $this->getAvatarImage($user);
-
- $vCard = new VCard();
- $vCard->VERSION = '3.0';
- $vCard->UID = $uid;
- if (!empty($displayName)) {
- $vCard->FN = $displayName;
- $vCard->N = $this->splitFullName($displayName);
- }
- if (!empty($emailAddress)) {
- $vCard->add(new Text($vCard, 'EMAIL', $emailAddress, ['TYPE' => 'OTHER']));
- }
- if (!empty($cloudId)) {
- $vCard->CLOUD = $cloudId;
- }
- if ($image) {
- $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]);
- }
- $vCard->validate();
-
- return $vCard;
+ public function __construct(AccountManager $accountManager) {
+ $this->accountManager = $accountManager;
}
/**
- * @param VCard $vCard
* @param IUser $user
- * @return bool
+ * @return VCard|null
*/
- public function updateCard(VCard $vCard, IUser $user) {
+ public function createCardFromUser(IUser $user) {
+
+ $userData = $this->accountManager->getUser($user);
+
$uid = $user->getUID();
- $displayName = $user->getDisplayName();
- $displayName = empty($displayName ) ? $uid : $displayName;
- $emailAddress = $user->getEMailAddress();
$cloudId = $user->getCloudId();
$image = $this->getAvatarImage($user);
- $updated = false;
- if($this->propertyNeedsUpdate($vCard, 'FN', $displayName)) {
- $vCard->FN = new Text($vCard, 'FN', $displayName);
- unset($vCard->N);
- $vCard->add(new Text($vCard, 'N', $this->splitFullName($displayName)));
- $updated = true;
- }
- if($this->propertyNeedsUpdate($vCard, 'EMAIL', $emailAddress)) {
- $vCard->EMAIL = new Text($vCard, 'EMAIL', $emailAddress);
- $updated = true;
- }
- if($this->propertyNeedsUpdate($vCard, 'CLOUD', $cloudId)) {
- $vCard->CLOUD = new Text($vCard, 'CLOUD', $cloudId);
- $updated = true;
- }
-
- if($this->propertyNeedsUpdate($vCard, 'PHOTO', $image)) {
- unset($vCard->PHOTO);
- $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]);
- $updated = true;
- }
-
- if (empty($emailAddress) && !is_null($vCard->EMAIL)) {
- unset($vCard->EMAIL);
- $updated = true;
- }
- if (empty($cloudId) && !is_null($vCard->CLOUD)) {
- unset($vCard->CLOUD);
- $updated = true;
- }
- if (empty($image) && !is_null($vCard->PHOTO)) {
- unset($vCard->PHOTO);
- $updated = true;
+ $vCard = new VCard();
+ $vCard->add(new Text($vCard, 'UID', $uid));
+
+ $publish = false;
+
+ foreach ($userData as $property => $value) {
+ if ($value['scope'] === AccountManager::VISIBILITY_CONTACTS_ONLY ||
+ $value['scope'] === AccountManager::VISIBILITY_PUBLIC
+ ) {
+ $publish = true;
+ switch ($property) {
+ case AccountManager::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:
+ if ($image !== null) {
+ $vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]);
+ }
+ break;
+ case AccountManager::PROPERTY_EMAIL:
+ $vCard->add(new Text($vCard, 'EMAIL', $value['value'], ['TYPE' => 'OTHER']));
+ break;
+ case AccountManager::PROPERTY_WEBSITE:
+ $vCard->add(new Text($vCard, 'URL', $value['value']));
+ break;
+ case AccountManager::PROPERTY_PHONE:
+ $vCard->add(new Text($vCard, 'TEL', $value['value'], ['TYPE' => 'OTHER']));
+ break;
+ case AccountManager::PROPERTY_ADDRESS:
+ $vCard->add(new Text($vCard, 'ADR', $value['value'], ['TYPE' => 'OTHER']));
+ break;
+ case AccountManager::PROPERTY_TWITTER:
+ $vCard->add(new Text($vCard, 'X-SOCIALPROFILE', $value['value'], ['TYPE' => 'TWITTER']));
+ break;
+ }
+ }
}
- return $updated;
- }
-
- /**
- * @param VCard $vCard
- * @param string $name
- * @param string|IImage $newValue
- * @return bool
- */
- private function propertyNeedsUpdate(VCard $vCard, $name, $newValue) {
- if (is_null($newValue)) {
- return false;
+ if ($publish && !empty($cloudId)) {
+ $vCard->add(new Text($vCard, 'CLOUD', $cloudId));
+ $vCard->validate();
+ return $vCard;
}
- $value = $vCard->__get($name);
- if (!is_null($value)) {
- $value = $value->getValue();
- $newValue = $newValue instanceof IImage ? $newValue->data() : $newValue;
- return $value !== $newValue;
- }
- return true;
+ return null;
}
/**
diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php
index 1ad37da6bfe..1293d8ae8a0 100644
--- a/apps/dav/lib/CardDAV/SyncService.php
+++ b/apps/dav/lib/CardDAV/SyncService.php
@@ -24,6 +24,7 @@
namespace OCA\DAV\CardDAV;
+use OC\Accounts\AccountManager;
use OCP\AppFramework\Http;
use OCP\ILogger;
use OCP\IUser;
@@ -48,10 +49,22 @@ class SyncService {
/** @var array */
private $localSystemAddressBook;
- public function __construct(CardDavBackend $backend, IUserManager $userManager, ILogger $logger) {
+ /** @var AccountManager */
+ private $accountManager;
+
+ /**
+ * SyncService constructor.
+ *
+ * @param CardDavBackend $backend
+ * @param IUserManager $userManager
+ * @param ILogger $logger
+ * @param AccountManager $accountManager
+ */
+ public function __construct(CardDavBackend $backend, IUserManager $userManager, ILogger $logger, AccountManager $accountManager) {
$this->backend = $backend;
$this->userManager = $userManager;
$this->logger = $logger;
+ $this->accountManager = $accountManager;
}
/**
@@ -215,7 +228,7 @@ class SyncService {
public function updateUser($user) {
$systemAddressBook = $this->getLocalSystemAddressBook();
$addressBookId = $systemAddressBook['id'];
- $converter = new Converter();
+ $converter = new Converter($this->accountManager);
$name = $user->getBackendClassName();
$userId = $user->getUID();
@@ -223,10 +236,14 @@ class SyncService {
$card = $this->backend->getCard($addressBookId, $cardId);
if ($card === false) {
$vCard = $converter->createCardFromUser($user);
- $this->backend->createCard($addressBookId, $cardId, $vCard->serialize());
+ if ($vCard !== null) {
+ $this->backend->createCard($addressBookId, $cardId, $vCard->serialize());
+ }
} else {
- $vCard = Reader::read($card['carddata']);
- if ($converter->updateCard($vCard, $user)) {
+ $vCard = $converter->createCardFromUser($user);
+ if (is_null($vCard)) {
+ $this->backend->deleteCard($addressBookId, $cardId);
+ } else {
$this->backend->updateCard($addressBookId, $cardId, $vCard->serialize());
}
}
diff --git a/apps/dav/lib/HookManager.php b/apps/dav/lib/HookManager.php
index 247d4b291af..26f3895a459 100644
--- a/apps/dav/lib/HookManager.php
+++ b/apps/dav/lib/HookManager.php
@@ -27,6 +27,8 @@ use OCA\DAV\CardDAV\SyncService;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Util;
+use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\GenericEvent;
class HookManager {
@@ -51,14 +53,19 @@ class HookManager {
/** @var array */
private $addressBooksToDelete;
+ /** @var EventDispatcher */
+ private $eventDispatcher;
+
public function __construct(IUserManager $userManager,
SyncService $syncService,
CalDavBackend $calDav,
- CardDavBackend $cardDav) {
+ CardDavBackend $cardDav,
+ EventDispatcher $eventDispatcher) {
$this->userManager = $userManager;
$this->syncService = $syncService;
$this->calDav = $calDav;
$this->cardDav = $cardDav;
+ $this->eventDispatcher = $eventDispatcher;
}
public function setup() {
diff --git a/apps/dav/tests/unit/CardDAV/ConverterTest.php b/apps/dav/tests/unit/CardDAV/ConverterTest.php
index 7737b999507..737bbd96aaa 100644
--- a/apps/dav/tests/unit/CardDAV/ConverterTest.php
+++ b/apps/dav/tests/unit/CardDAV/ConverterTest.php
@@ -24,79 +24,121 @@
namespace OCA\DAV\Tests\unit\CardDAV;
+use OC\Accounts\AccountManager;
use OCA\DAV\CardDAV\Converter;
+use OCP\IDBConnection;
use OCP\IImage;
use OCP\IUser;
+use OpenCloud\ObjectStore\Resource\Account;
use PHPUnit_Framework_MockObject_MockObject;
+use Symfony\Component\EventDispatcher\EventDispatcher;
use Test\TestCase;
class ConverterTest extends TestCase {
- /**
- * @dataProvider providesNewUsers
- */
- public function testCreation($expectedVCard, $displayName = null, $eMailAddress = null, $cloudId = null) {
- $user = $this->getUserMock($displayName, $eMailAddress, $cloudId);
+ /** @var AccountManager | PHPUnit_Framework_MockObject_MockObject */
+ private $accountManager;
- $converter = new Converter();
- $vCard = $converter->createCardFromUser($user);
- $cardData = $vCard->serialize();
+ /** @var EventDispatcher | PHPUnit_Framework_MockObject_MockObject */
+ private $eventDispatcher;
- $this->assertEquals($expectedVCard, $cardData);
- }
+ /** @var IDBConnection | PHPUnit_Framework_MockObject_MockObject */
+ private $databaseConnection;
- public function providesNewUsers() {
- return [
- ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n"],
- ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
- ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nEMAIL;TYPE=OTHER:foo@bar.net\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar", "foo@bar.net"],
- ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:Dr. Foo Bar\r\nN:Bar;Dr.;Foo;;\r\nCLOUD:foo@bar.net\r\nPHOTO;ENCODING=b;TYPE=JPEG:MTIzNDU2Nzg5\r\nEND:VCARD\r\n", "Dr. Foo Bar", null, "foo@bar.net"],
- ];
+ public function setUp() {
+ parent::setUp();
+ $this->databaseConnection = $this->getMockBuilder('OCP\IDBConnection')->getMock();
+ $this->eventDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcher')
+ ->disableOriginalConstructor()->getMock();
+ $this->accountManager = $this->getMockBuilder('OC\Accounts\AccountManager')
+ ->disableOriginalConstructor()->getMock();
}
- /**
- * @dataProvider providesNewUsers
- */
- public function testUpdateOfUnchangedUser($expectedVCard, $displayName = null, $eMailAddress = null, $cloudId = null) {
- $user = $this->getUserMock($displayName, $eMailAddress, $cloudId);
-
- $converter = new Converter();
- $vCard = $converter->createCardFromUser($user);
- $updated = $converter->updateCard($vCard, $user);
- $this->assertFalse($updated);
- $cardData = $vCard->serialize();
-
- $this->assertEquals($expectedVCard, $cardData);
+ public function getAccountManager(IUser $user) {
+ $accountManager = $this->getMockBuilder('OC\Accounts\AccountManager')
+ ->disableOriginalConstructor()->getMock();
+ $accountManager->expects($this->any())->method('getUser')->willReturn(
+ [
+ AccountManager::PROPERTY_DISPLAYNAME =>
+ [
+ 'value' => $user->getDisplayName(),
+ 'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
+ ],
+ AccountManager::PROPERTY_ADDRESS =>
+ [
+ 'value' => '',
+ 'scope' => AccountManager::VISIBILITY_PRIVATE,
+ ],
+ AccountManager::PROPERTY_WEBSITE =>
+ [
+ 'value' => '',
+ 'scope' => AccountManager::VISIBILITY_PRIVATE,
+ ],
+ AccountManager::PROPERTY_EMAIL =>
+ [
+ 'value' => $user->getEMailAddress(),
+ 'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
+ ],
+ AccountManager::PROPERTY_AVATAR =>
+ [
+ 'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY
+ ],
+ AccountManager::PROPERTY_PHONE =>
+ [
+ 'value' => '',
+ 'scope' => AccountManager::VISIBILITY_PRIVATE,
+ ],
+ AccountManager::PROPERTY_TWITTER =>
+ [
+ 'value' => '',
+ 'scope' => AccountManager::VISIBILITY_PRIVATE,
+ ],
+ ]
+ );
+
+ return $accountManager;
}
/**
- * @dataProvider providesUsersForUpdateOfRemovedElement
+ * @dataProvider providesNewUsers
*/
- public function testUpdateOfRemovedElement($expectedVCard, $displayName = null, $eMailAddress = null, $cloudId = null) {
+ public function testCreation($expectedVCard, $displayName = null, $eMailAddress = null, $cloudId = null) {
$user = $this->getUserMock($displayName, $eMailAddress, $cloudId);
+ $accountManager = $this->getAccountManager($user);
- $converter = new Converter();
+ $converter = new Converter($accountManager);
$vCard = $converter->createCardFromUser($user);
+ if ($expectedVCard !== null) {
+ $this->assertInstanceOf('Sabre\VObject\Component\VCard', $vCard);
+ $cardData = $vCard->jsonSerialize();
+ $this->compareData($expectedVCard, $cardData);
- $user1 = $this->getMockBuilder('OCP\IUser')->disableOriginalConstructor()->getMock();
- $user1->method('getUID')->willReturn('12345');
- $user1->method('getDisplayName')->willReturn(null);
- $user1->method('getEMailAddress')->willReturn(null);
- $user1->method('getCloudId')->willReturn(null);
- $user1->method('getAvatarImage')->willReturn(null);
+ } else {
+ $this->assertSame($expectedVCard, $vCard);
+ }
- $updated = $converter->updateCard($vCard, $user1);
- $this->assertTrue($updated);
- $cardData = $vCard->serialize();
+ }
- $this->assertEquals($expectedVCard, $cardData);
+ protected function compareData($expected, $data) {
+ foreach ($expected as $key => $value) {
+ $found = false;
+ foreach ($data[1] as $d) {
+ if($d[0] === $key && $d[3] === $value) {
+ $found = true;
+ break;
+ }
+ }
+ if (!$found) $this->assertTrue(false, 'Expected data: ' . $key . ' not found.');
+ }
}
- public function providesUsersForUpdateOfRemovedElement() {
+ public function providesNewUsers() {
return [
- ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar"],
- ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar", "foo@bar.net"],
- ["BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", "Dr. Foo Bar", null, "foo@bar.net"],
+ [null],
+ [null, null, 'foo@bar.net'],
+ [['cloud' => 'foo@cloud.net', 'email' => 'foo@bar.net'], null, 'foo@bar.net', 'foo@cloud.net'],
+ [['cloud' => 'foo@cloud.net', 'email' => 'foo@bar.net', 'fn' => 'Dr. Foo Bar'], "Dr. Foo Bar", "foo@bar.net", 'foo@cloud.net'],
+ [['cloud' => 'foo@cloud.net', 'fn' => 'Dr. Foo Bar'], "Dr. Foo Bar", null, "foo@cloud.net"],
];
}
@@ -107,7 +149,7 @@ class ConverterTest extends TestCase {
*/
public function testNameSplitter($expected, $fullName) {
- $converter = new Converter();
+ $converter = new Converter($this->accountManager);
$r = $converter->splitFullName($fullName);
$r = implode(';', $r);
$this->assertEquals($expected, $r);
diff --git a/apps/dav/tests/unit/CardDAV/SyncServiceTest.php b/apps/dav/tests/unit/CardDAV/SyncServiceTest.php
index e6a5ac1f16a..68345def66b 100644
--- a/apps/dav/tests/unit/CardDAV/SyncServiceTest.php
+++ b/apps/dav/tests/unit/CardDAV/SyncServiceTest.php
@@ -25,6 +25,7 @@
namespace OCA\DAV\Tests\unit\CardDAV;
+use OC\Accounts\AccountManager;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\CardDAV\SyncService;
use OCP\IUser;
@@ -76,7 +77,8 @@ class SyncServiceTest extends TestCase {
/** @var IUserManager $userManager */
$userManager = $this->getMockBuilder('OCP\IUserManager')->disableOriginalConstructor()->getMock();
$logger = $this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock();
- $ss = new SyncService($backend, $userManager, $logger);
+ $accountManager = $this->getMockBuilder('OC\Accounts\AccountManager')->disableOriginalConstructor()->getMock();
+ $ss = new SyncService($backend, $userManager, $logger, $accountManager);
$book = $ss->ensureSystemAddressBookExists('principals/users/adam', 'contacts', []);
}
@@ -100,8 +102,47 @@ class SyncServiceTest extends TestCase {
$user = $this->getMockBuilder('OCP\IUser')->disableOriginalConstructor()->getMock();
$user->method('getBackendClassName')->willReturn('unittest');
$user->method('getUID')->willReturn('test-user');
-
- $ss = new SyncService($backend, $userManager, $logger);
+ $user->method('getCloudId')->willReturn('cloudId');
+ $accountManager = $this->getMockBuilder('OC\Accounts\AccountManager')->disableOriginalConstructor()->getMock();
+ $accountManager->expects($this->any())->method('getUser')
+ ->willReturn([
+ AccountManager::PROPERTY_DISPLAYNAME =>
+ [
+ 'value' => $user->getDisplayName(),
+ 'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
+ ],
+ AccountManager::PROPERTY_ADDRESS =>
+ [
+ 'value' => '',
+ 'scope' => AccountManager::VISIBILITY_PRIVATE,
+ ],
+ AccountManager::PROPERTY_WEBSITE =>
+ [
+ 'value' => '',
+ 'scope' => AccountManager::VISIBILITY_PRIVATE,
+ ],
+ AccountManager::PROPERTY_EMAIL =>
+ [
+ 'value' => $user->getEMailAddress(),
+ 'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY,
+ ],
+ AccountManager::PROPERTY_AVATAR =>
+ [
+ 'scope' => AccountManager::VISIBILITY_CONTACTS_ONLY
+ ],
+ AccountManager::PROPERTY_PHONE =>
+ [
+ 'value' => '',
+ 'scope' => AccountManager::VISIBILITY_PRIVATE,
+ ],
+ AccountManager::PROPERTY_TWITTER =>
+ [
+ 'value' => '',
+ 'scope' => AccountManager::VISIBILITY_PRIVATE,
+ ],
+ ]);
+
+ $ss = new SyncService($backend, $userManager, $logger, $accountManager);
$ss->updateUser($user);
$user->method('getDisplayName')->willReturn('A test user for unit testing');
@@ -135,10 +176,11 @@ class SyncServiceTest extends TestCase {
private function getSyncServiceMock($backend, $response) {
$userManager = $this->getMockBuilder('OCP\IUserManager')->disableOriginalConstructor()->getMock();
$logger = $this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock();
+ $accountManager = $this->getMockBuilder('OC\Accounts\AccountManager')->disableOriginalConstructor()->getMock();
/** @var SyncService | \PHPUnit_Framework_MockObject_MockObject $ss */
$ss = $this->getMockBuilder(SyncService::class)
->setMethods(['ensureSystemAddressBookExists', 'requestSyncReport', 'download'])
- ->setConstructorArgs([$backend, $userManager, $logger])
+ ->setConstructorArgs([$backend, $userManager, $logger, $accountManager])
->getMock();
$ss->method('requestSyncReport')->withAnyParameters()->willReturn(['response' => $response, 'token' => 'sync-token-1']);
$ss->method('ensureSystemAddressBookExists')->willReturn(['id' => 1]);
diff --git a/apps/dav/tests/unit/DAV/HookManagerTest.php b/apps/dav/tests/unit/DAV/HookManagerTest.php
index f980e595bf9..9579ce1c6d3 100644
--- a/apps/dav/tests/unit/DAV/HookManagerTest.php
+++ b/apps/dav/tests/unit/DAV/HookManagerTest.php
@@ -31,14 +31,19 @@ use OCA\DAV\HookManager;
use OCP\IL10N;
use OCP\IUser;
use OCP\IUserManager;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Test\TestCase;
class HookManagerTest extends TestCase {
/** @var IL10N */
private $l10n;
+ /** @var EventDispatcher | \PHPUnit_Framework_MockObject_MockObject */
+ private $eventDispatcher;
+
public function setUp() {
parent::setUp();
+ $this->eventDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcher')->disableOriginalConstructor()->getMock();
$this->l10n = $this->createMock(IL10N::class);
$this->l10n
->expects($this->any())
@@ -82,7 +87,7 @@ class HookManagerTest extends TestCase {
'principals/users/newUser',
'contacts', ['{DAV:}displayname' => 'Contacts']);
- $hm = new HookManager($userManager, $syncService, $cal, $card);
+ $hm = new HookManager($userManager, $syncService, $cal, $card, $this->eventDispatcher);
$hm->firstLogin($user);
}
@@ -116,7 +121,7 @@ class HookManagerTest extends TestCase {
$card->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(1);
$card->expects($this->never())->method('createAddressBook');
- $hm = new HookManager($userManager, $syncService, $cal, $card);
+ $hm = new HookManager($userManager, $syncService, $cal, $card, $this->eventDispatcher);
$hm->firstLogin($user);
}
@@ -154,7 +159,7 @@ class HookManagerTest extends TestCase {
'principals/users/newUser',
'contacts', ['{DAV:}displayname' => 'Contacts']);
- $hm = new HookManager($userManager, $syncService, $cal, $card);
+ $hm = new HookManager($userManager, $syncService, $cal, $card, $this->eventDispatcher);
$hm->firstLogin($user);
}
@@ -195,7 +200,7 @@ class HookManagerTest extends TestCase {
]);
$card->expects($this->once())->method('deleteAddressBook');
- $hm = new HookManager($userManager, $syncService, $cal, $card, $this->l10n);
+ $hm = new HookManager($userManager, $syncService, $cal, $card, $this->eventDispatcher);
$hm->preDeleteUser(['uid' => 'newUser']);
$hm->postDeleteUser(['uid' => 'newUser']);
}
diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php
index 6481abe32e2..61f1b1c8f18 100644
--- a/apps/federatedfilesharing/lib/FederatedShareProvider.php
+++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php
@@ -943,4 +943,14 @@ class FederatedShareProvider implements IShareProvider {
$result = $this->config->getAppValue('files_sharing', 'incoming_server2server_share_enabled', 'yes');
return ($result === 'yes') ? true : false;
}
+
+ /**
+ * Check if querying sharees on the lookup server is enabled
+ *
+ * @return bool
+ */
+ public function isLookupServerQueriesEnabled() {
+ $result = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no');
+ return ($result === 'yes') ? true : false;
+ }
}
diff --git a/apps/federatedfilesharing/lib/Settings/Admin.php b/apps/federatedfilesharing/lib/Settings/Admin.php
index 64619e329f3..20ff6ae6f4a 100644
--- a/apps/federatedfilesharing/lib/Settings/Admin.php
+++ b/apps/federatedfilesharing/lib/Settings/Admin.php
@@ -43,6 +43,7 @@ class Admin implements ISettings {
$parameters = [
'outgoingServer2serverShareEnabled' => $this->fedShareProvider->isOutgoingServer2serverShareEnabled(),
'incomingServer2serverShareEnabled' => $this->fedShareProvider->isIncomingServer2serverShareEnabled(),
+ 'lookupServerEnabled' => $this->fedShareProvider->isLookupServerQueriesEnabled(),
];
return new TemplateResponse('federatedfilesharing', 'settings-admin', $parameters, '');
diff --git a/apps/federatedfilesharing/templates/settings-admin.php b/apps/federatedfilesharing/templates/settings-admin.php
index 3ffd4bd5b27..0670553e061 100644
--- a/apps/federatedfilesharing/templates/settings-admin.php
+++ b/apps/federatedfilesharing/templates/settings-admin.php
@@ -25,4 +25,11 @@ script('federatedfilesharing', 'settings-admin');
<?php p($l->t('Allow users on this server to receive shares from other servers'));?>
</label><br/>
</p>
+ <p>
+ <input type="checkbox" name="lookupServerEnabled" id="lookupServerEnabled" class="checkbox"
+ value="1" <?php if ($_['lookupServerEnabled']) print_unescaped('checked="checked"'); ?> />
+ <label for="lookupServerEnabled">
+ <?php p($l->t('Enable lookups on lookup server'));?>
+ </label><br/>
+ </p>
</div>
diff --git a/apps/federatedfilesharing/tests/Settings/AdminTest.php b/apps/federatedfilesharing/tests/Settings/AdminTest.php
index 60fadca7b56..c0b35a6427b 100644
--- a/apps/federatedfilesharing/tests/Settings/AdminTest.php
+++ b/apps/federatedfilesharing/tests/Settings/AdminTest.php
@@ -65,10 +65,15 @@ class AdminTest extends TestCase {
->expects($this->once())
->method('isIncomingServer2serverShareEnabled')
->willReturn($state);
+ $this->federatedShareProvider
+ ->expects($this->once())
+ ->method('isLookupServerQueriesEnabled')
+ ->willReturn($state);
$params = [
'outgoingServer2serverShareEnabled' => $state,
'incomingServer2serverShareEnabled' => $state,
+ 'lookupServerEnabled' => $state,
];
$expected = new TemplateResponse('federatedfilesharing', 'settings-admin', $params, '');
$this->assertEquals($expected, $this->admin->getForm());
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index 9b844919c4e..c0460ff6058 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -550,7 +550,7 @@ html.ie8 #fileList tr.selected td.filename>.selectCheckBox {
}
.bubble:after,
#app-navigation .app-navigation-entry-menu:after {
- right: 6px;
+ right: 12px;
}
.bubble:before,
#app-navigation .app-navigation-entry-menu:before {
@@ -625,13 +625,6 @@ html.ie8 .column-mtime .selectedActions {
padding-right: 14px;
}
-#fileList .popovermenu {
- margin-right: 6px;
-}
-.ie8 #fileList .popovermenu {
- margin-top: -10px;
-}
-
.ie8 #fileList a.action img,
#fileList tr:hover a.action,
#fileList a.action.permanent,
@@ -649,7 +642,6 @@ html.ie8 .column-mtime .selectedActions {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
filter: alpha(opacity=30);
opacity: .3;
- display:inline;
}
.ie8 #fileList a.action:hover img,
#fileList tr a.action.disabled.action-download,
@@ -673,6 +665,7 @@ html.ie8 .column-mtime .selectedActions {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)" !important;
filter: alpha(opacity=70) !important;
opacity: .7 !important;
+ display:inline;
}
/* always show actions on mobile, not only on hover */
#fileList a.action.action-menu.permanent {
@@ -775,11 +768,6 @@ table.dragshadow td.size {
padding: initial;
}
-#fileList .popovermenu a.action {
- padding: 10px;
- margin: -10px;
-}
-
html.ie8 #controls .button.new {
padding-right: 0;
}
@@ -826,11 +814,7 @@ html.ie8 #controls .button.new {
}
#fileList .popovermenu .action {
- display: block;
- line-height: 30px;
- padding-left: 5px;
color: #000;
- padding: 0;
}
#filestable .filename .action .icon,
diff --git a/apps/files_sharing/lib/Controller/ShareesAPIController.php b/apps/files_sharing/lib/Controller/ShareesAPIController.php
index 09912b7758a..6b3208dc289 100644
--- a/apps/files_sharing/lib/Controller/ShareesAPIController.php
+++ b/apps/files_sharing/lib/Controller/ShareesAPIController.php
@@ -28,6 +28,7 @@ use OCP\AppFramework\Http;
use OCP\AppFramework\OCS\OCSBadRequestException;
use OCP\AppFramework\OCSController;
use OCP\Contacts\IManager;
+use OCP\Http\Client\IClientService;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\ILogger;
@@ -65,6 +66,9 @@ class ShareesAPIController extends OCSController {
/** @var \OCP\Share\IManager */
protected $shareManager;
+ /** @var IClientService */
+ protected $clientService;
+
/** @var bool */
protected $shareWithGroupOnly = false;
@@ -89,6 +93,7 @@ class ShareesAPIController extends OCSController {
'groups' => [],
'remotes' => [],
'emails' => [],
+ 'lookup' => [],
];
protected $reachedEndFor = [];
@@ -104,6 +109,7 @@ class ShareesAPIController extends OCSController {
* @param IURLGenerator $urlGenerator
* @param ILogger $logger
* @param \OCP\Share\IManager $shareManager
+ * @param IClientService $clientService
*/
public function __construct($appName,
IRequest $request,
@@ -114,7 +120,8 @@ class ShareesAPIController extends OCSController {
IUserSession $userSession,
IURLGenerator $urlGenerator,
ILogger $logger,
- \OCP\Share\IManager $shareManager) {
+ \OCP\Share\IManager $shareManager,
+ IClientService $clientService) {
parent::__construct($appName, $request);
$this->groupManager = $groupManager;
@@ -125,6 +132,7 @@ class ShareesAPIController extends OCSController {
$this->urlGenerator = $urlGenerator;
$this->logger = $logger;
$this->shareManager = $shareManager;
+ $this->clientService = $clientService;
}
/**
@@ -414,10 +422,11 @@ class ShareesAPIController extends OCSController {
* @param int $page
* @param int $perPage
* @param int|int[] $shareType
+ * @param bool $lookup
* @return Http\DataResponse
* @throws OCSBadRequestException
*/
- public function search($search = '', $itemType = null, $page = 1, $perPage = 200, $shareType = null) {
+ public function search($search = '', $itemType = null, $page = 1, $perPage = 200, $shareType = null, $lookup = true) {
if ($perPage <= 0) {
throw new OCSBadRequestException('Invalid perPage argument');
}
@@ -459,7 +468,7 @@ class ShareesAPIController extends OCSController {
$this->limit = (int) $perPage;
$this->offset = $perPage * ($page - 1);
- return $this->searchSharees($search, $itemType, $shareTypes, $page, $perPage);
+ return $this->searchSharees($search, $itemType, $shareTypes, $page, $perPage, $lookup);
}
/**
@@ -485,10 +494,11 @@ class ShareesAPIController extends OCSController {
* @param array $shareTypes
* @param int $page
* @param int $perPage
+ * @param bool $lookup
* @return Http\DataResponse
* @throws OCSBadRequestException
*/
- protected function searchSharees($search, $itemType, array $shareTypes, $page, $perPage) {
+ protected function searchSharees($search, $itemType, array $shareTypes, $page, $perPage, $lookup) {
// Verify arguments
if ($itemType === null) {
throw new OCSBadRequestException('Missing itemType');
@@ -510,11 +520,17 @@ class ShareesAPIController extends OCSController {
$remoteResults = $this->getRemote($search);
}
+ // Get emails
$mailResults = ['results' => [], 'exact' => [], 'exactIdMatch' => false];
if (in_array(Share::SHARE_TYPE_EMAIL, $shareTypes)) {
$mailResults = $this->getEmail($search);
}
+ // Get from lookup server
+ if ($lookup) {
+ $this->getLookup($search);
+ }
+
// if we have a exact match, either for the federated cloud id or for the
// email address we only return the exact match. It is highly unlikely
// that the exact same email address and federated cloud id exists
@@ -609,6 +625,40 @@ class ShareesAPIController extends OCSController {
return $result;
}
+ protected function getLookup($search) {
+ $isEnabled = $this->config->getAppValue('files_sharing', 'lookupServerEnabled', 'no');
+ $result = [];
+
+ if($isEnabled === 'yes') {
+ try {
+ $client = $this->clientService->newClient();
+ $response = $client->get(
+ 'https://lookup.nextcloud.com/users?search=' . urlencode($search),
+ [
+ 'timeout' => 10,
+ 'connect_timeout' => 3,
+ ]
+ );
+
+ $body = json_decode($response->getBody(), true);
+
+ $result = [];
+ foreach ($body as $lookup) {
+ $result[] = [
+ 'label' => $lookup['federationId'],
+ 'value' => [
+ 'shareType' => Share::SHARE_TYPE_REMOTE,
+ 'shareWith' => $lookup['federationId'],
+ ],
+ 'extra' => $lookup,
+ ];
+ }
+ } catch (\Exception $e) {}
+ }
+
+ $this->result['lookup'] = $result;
+ }
+
/**
* Generates a bunch of pagination links for the current page
*
diff --git a/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php
index 336dcb70f0e..c570cb16980 100644
--- a/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php
+++ b/apps/files_sharing/tests/Controller/ShareesAPIControllerTest.php
@@ -29,6 +29,7 @@ use OCA\Files_Sharing\Controller\ShareesAPIController;
use OCA\Files_Sharing\Tests\TestCase;
use OCP\AppFramework\Http;
use OCP\AppFramework\OCS\OCSBadRequestException;
+use OCP\Http\Client\IClientService;
use OCP\Share;
/**
@@ -60,6 +61,9 @@ class ShareesAPIControllerTest extends TestCase {
/** @var \OCP\Share\IManager|\PHPUnit_Framework_MockObject_MockObject */
protected $shareManager;
+ /** @var IClientService|\PHPUnit_Framework_MockObject_MockObject */
+ private $clientService;
+
protected function setUp() {
parent::setUp();
@@ -87,6 +91,8 @@ class ShareesAPIControllerTest extends TestCase {
->disableOriginalConstructor()
->getMock();
+ $this->clientService = $this->createMock(IClientService::class);
+
$this->sharees = new ShareesAPIController(
'files_sharing',
$this->request,
@@ -97,7 +103,8 @@ class ShareesAPIControllerTest extends TestCase {
$this->session,
$this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(),
$this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock(),
- $this->shareManager
+ $this->shareManager,
+ $this->clientService
);
}
@@ -1386,7 +1393,8 @@ class ShareesAPIControllerTest extends TestCase {
$this->session,
$this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(),
$this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock(),
- $this->shareManager
+ $this->shareManager,
+ $this->clientService
])
->setMethods(array('searchSharees', 'isRemoteSharingAllowed', 'shareProviderExists'))
->getMock();
@@ -1477,7 +1485,8 @@ class ShareesAPIControllerTest extends TestCase {
$this->session,
$this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(),
$this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock(),
- $this->shareManager
+ $this->shareManager,
+ $this->clientService
])
->setMethods(array('searchSharees', 'isRemoteSharingAllowed'))
->getMock();
@@ -1522,6 +1531,7 @@ class ShareesAPIControllerTest extends TestCase {
'groups' => [],
'remotes' => [],
'emails' => [],
+ 'lookup' => [],
], false],
['test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, false, [], [], ['results' => [], 'exact' => [], 'exactIdMatch' => false],
[
@@ -1530,6 +1540,7 @@ class ShareesAPIControllerTest extends TestCase {
'groups' => [],
'remotes' => [],
'emails' => [],
+ 'lookup' => [],
], false],
[
'test', 'folder', [Share::SHARE_TYPE_USER, Share::SHARE_TYPE_GROUP, Share::SHARE_TYPE_REMOTE], 1, 2, false, [
@@ -1551,6 +1562,7 @@ class ShareesAPIControllerTest extends TestCase {
['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']],
],
'emails' => [],
+ 'lookup' => [],
], true,
],
// No groups requested
@@ -1570,6 +1582,7 @@ class ShareesAPIControllerTest extends TestCase {
['label' => 'testz@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'testz@remote']],
],
'emails' => [],
+ 'lookup' => [],
], false,
],
// Share type restricted to user - Only one user
@@ -1585,6 +1598,7 @@ class ShareesAPIControllerTest extends TestCase {
'groups' => [],
'remotes' => [],
'emails' => [],
+ 'lookup' => [],
], false,
],
// Share type restricted to user - Multipage result
@@ -1602,6 +1616,7 @@ class ShareesAPIControllerTest extends TestCase {
'groups' => [],
'remotes' => [],
'emails' => [],
+ 'lookup' => [],
], true,
],
];
@@ -1636,7 +1651,8 @@ class ShareesAPIControllerTest extends TestCase {
$this->session,
$this->getMockBuilder('OCP\IURLGenerator')->disableOriginalConstructor()->getMock(),
$this->getMockBuilder('OCP\ILogger')->disableOriginalConstructor()->getMock(),
- $this->shareManager
+ $this->shareManager,
+ $this->clientService
])
->setMethods(array('getShareesForShareIds', 'getUsers', 'getGroups', 'getRemote'))
->getMock();
diff --git a/apps/lookup_server_connector/appinfo/app.php b/apps/lookup_server_connector/appinfo/app.php
new file mode 100644
index 00000000000..6c63e9a0400
--- /dev/null
+++ b/apps/lookup_server_connector/appinfo/app.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
+ *
+ * @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/>.
+ *
+ */
+
+$dispatcher = \OC::$server->getEventDispatcher();
+
+$dispatcher->addListener('OC\AccountManager::userUpdated', function(\Symfony\Component\EventDispatcher\GenericEvent $event) {
+ $user = $event->getSubject();
+
+ $keyManager = new \OC\Security\IdentityProof\Manager(
+ \OC::$server->getAppDataDir('identityproof'),
+ \OC::$server->getCrypto()
+ );
+ $updateLookupServer = new \OCA\LookupServerConnector\UpdateLookupServer(
+ new \OC\Accounts\AccountManager(\OC::$server->getDatabaseConnection(), \OC::$server->getEventDispatcher()),
+ \OC::$server->getConfig(),
+ \OC::$server->getSecureRandom(),
+ \OC::$server->getHTTPClientService(),
+ $keyManager,
+ new \OC\Security\IdentityProof\Signer(
+ $keyManager,
+ new \OC\AppFramework\Utility\TimeFactory(),
+ \OC::$server->getURLGenerator(),
+ \OC::$server->getUserManager()
+ ),
+ \OC::$server->getJobList()
+ );
+ $updateLookupServer->userUpdated($user);
+});
diff --git a/apps/lookup_server_connector/appinfo/info.xml b/apps/lookup_server_connector/appinfo/info.xml
new file mode 100644
index 00000000000..88898c0b71b
--- /dev/null
+++ b/apps/lookup_server_connector/appinfo/info.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<info>
+ <id>lookup_server_connector</id>
+ <name>Lookup Server Connector</name>
+ <description>Sync public user information with the lookup server</description>
+ <licence>AGPL</licence>
+ <author>Bjoern Schiessle</author>
+ <namespace>LookupServerConnector</namespace>
+ <version>1.0.0</version>
+ <category>other</category>
+ <dependencies>
+ <owncloud min-version="11.0" max-version="11.0" />
+ </dependencies>
+ <default_enable/>
+ <types>
+ <authentication/>
+ </types>
+</info>
diff --git a/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php b/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php
new file mode 100644
index 00000000000..f33323b2d4f
--- /dev/null
+++ b/apps/lookup_server_connector/lib/BackgroundJobs/RetryJob.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
+ *
+ * @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 OCA\LookupServerConnector\BackgroundJobs;
+
+
+use OC\BackgroundJob\Job;
+use OCP\BackgroundJob\IJobList;
+use OCP\Http\Client\IClientService;
+
+class RetryJob extends Job {
+ /** @var IClientService */
+ private $clientService;
+ /** @var IJobList */
+ private $jobList;
+ /** @var string */
+ private $lookupServer = 'https://lookup.nextcloud.com/users';
+
+ /**
+ * @param IClientService|null $clientService
+ * @param IJobList|null $jobList
+ */
+ public function __construct(IClientService $clientService = null,
+ IJobList $jobList = null) {
+ if($clientService !== null) {
+ $this->clientService = $clientService;
+ } else {
+ $this->clientService = \OC::$server->getHTTPClientService();
+ }
+ if($jobList !== null) {
+ $this->jobList = $jobList;
+ } else {
+ $this->jobList = \OC::$server->getJobList();
+ }
+ }
+
+ protected function run($argument) {
+ if($argument['retryNo'] === 5) {
+ return;
+ }
+
+ $client = $this->clientService->newClient();
+
+ try {
+ $client->post($this->lookupServer,
+ [
+ 'body' => json_encode($argument['dataArray']),
+ 'timeout' => 10,
+ 'connect_timeout' => 3,
+ ]
+ );
+ } catch (\Exception $e) {
+ $this->jobList->add(RetryJob::class,
+ [
+ 'dataArray' => $argument['dataArray'],
+ 'retryNo' => $argument['retryNo'] + 1,
+ ]
+ );
+
+ }
+ }
+}
diff --git a/apps/lookup_server_connector/lib/UpdateLookupServer.php b/apps/lookup_server_connector/lib/UpdateLookupServer.php
new file mode 100644
index 00000000000..7ba63c85567
--- /dev/null
+++ b/apps/lookup_server_connector/lib/UpdateLookupServer.php
@@ -0,0 +1,136 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Bjoern Schiessle <bjoern@schiessle.org>
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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 OCA\LookupServerConnector;
+
+use OC\Accounts\AccountManager;
+use OC\Security\IdentityProof\Manager;
+use OC\Security\IdentityProof\Signer;
+use OCA\LookupServerConnector\BackgroundJobs\RetryJob;
+use OCP\BackgroundJob\IJobList;
+use OCP\Http\Client\IClientService;
+use OCP\IConfig;
+use OCP\IUser;
+use OCP\Security\ISecureRandom;
+
+/**
+ * Class UpdateLookupServer
+ *
+ * @package OCA\LookupServerConnector
+ */
+class UpdateLookupServer {
+ /** @var AccountManager */
+ private $accountManager;
+ /** @var IConfig */
+ private $config;
+ /** @var ISecureRandom */
+ private $secureRandom;
+ /** @var IClientService */
+ private $clientService;
+ /** @var Manager */
+ private $keyManager;
+ /** @var Signer */
+ private $signer;
+ /** @var IJobList */
+ private $jobList;
+ /** @var string URL point to lookup server */
+ private $lookupServer = 'https://lookup.nextcloud.com/users';
+
+ /**
+ * @param AccountManager $accountManager
+ * @param IConfig $config
+ * @param ISecureRandom $secureRandom
+ * @param IClientService $clientService
+ * @param Manager $manager
+ * @param Signer $signer
+ * @param IJobList $jobList
+ */
+ public function __construct(AccountManager $accountManager,
+ IConfig $config,
+ ISecureRandom $secureRandom,
+ IClientService $clientService,
+ Manager $manager,
+ Signer $signer,
+ IJobList $jobList) {
+ $this->accountManager = $accountManager;
+ $this->config = $config;
+ $this->secureRandom = $secureRandom;
+ $this->clientService = $clientService;
+ $this->keyManager = $manager;
+ $this->signer = $signer;
+ $this->jobList = $jobList;
+ }
+
+ /**
+ * @param IUser $user
+ */
+ public function userUpdated(IUser $user) {
+ $userData = $this->accountManager->getUser($user);
+ $publicData = [];
+
+ foreach ($userData as $key => $data) {
+ if ($data['scope'] === AccountManager::VISIBILITY_PUBLIC) {
+ $publicData[$key] = $data;
+ }
+ }
+
+ if (!empty($publicData)) {
+ $this->sendToLookupServer($user, $publicData);
+ }
+ }
+
+ /**
+ * send public user data to the lookup server
+ *
+ * @param IUser $user
+ * @param array $publicData
+ */
+ protected function sendToLookupServer(IUser $user, array $publicData) {
+ $dataArray = [
+ 'federationId' => $user->getCloudId(),
+ 'name' => isset($publicData[AccountManager::PROPERTY_DISPLAYNAME]) ? $publicData[AccountManager::PROPERTY_DISPLAYNAME]['value'] : '',
+ 'email' => isset($publicData[AccountManager::PROPERTY_EMAIL]) ? $publicData[AccountManager::PROPERTY_EMAIL]['value'] : '',
+ 'address' => isset($publicData[AccountManager::PROPERTY_ADDRESS]) ? $publicData[AccountManager::PROPERTY_ADDRESS]['value'] : '',
+ 'website' => isset($publicData[AccountManager::PROPERTY_WEBSITE]) ? $publicData[AccountManager::PROPERTY_WEBSITE]['value'] : '',
+ 'twitter' => isset($publicData[AccountManager::PROPERTY_TWITTER]) ? $publicData[AccountManager::PROPERTY_TWITTER]['value'] : '',
+ 'phone' => isset($publicData[AccountManager::PROPERTY_PHONE]) ? $publicData[AccountManager::PROPERTY_PHONE]['value'] : '',
+ ];
+ $dataArray = $this->signer->sign('lookupserver', $dataArray, $user);
+ $httpClient = $this->clientService->newClient();
+ try {
+ $httpClient->post($this->lookupServer,
+ [
+ 'body' => json_encode($dataArray),
+ 'timeout' => 10,
+ 'connect_timeout' => 3,
+ ]
+ );
+ } catch (\Exception $e) {
+ $this->jobList->add(RetryJob::class,
+ [
+ 'dataArray' => $dataArray,
+ 'retryNo' => 0,
+ ]
+ );
+ }
+ }
+}
diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature
index 9103a71ab92..a8501ee8873 100644
--- a/build/integration/features/provisioning-v1.feature
+++ b/build/integration/features/provisioning-v1.feature
@@ -290,6 +290,7 @@ Feature: provisioning
| files_sharing |
| files_trashbin |
| files_versions |
+ | lookup_server_connector |
| provisioning_api |
| sharebymail |
| systemtags |
diff --git a/core/Application.php b/core/Application.php
index f68f7929e6a..545b5fe420b 100644
--- a/core/Application.php
+++ b/core/Application.php
@@ -30,7 +30,10 @@
namespace OC\Core;
+use OC\AppFramework\Utility\SimpleContainer;
+use OC\Security\IdentityProof\Manager;
use OCP\AppFramework\App;
+use OCP\Files\IAppData;
use OCP\Util;
/**
@@ -45,8 +48,14 @@ class Application extends App {
$container = $this->getContainer();
- $container->registerService('defaultMailAddress', function() {
+ $container->registerService('defaultMailAddress', function () {
return Util::getDefaultEmailAddress('lostpassword-noreply');
});
+ $container->registerService(Manager::class, function () {
+ return new Manager(
+ \OC::$server->getAppDataDir('identityproof'),
+ \OC::$server->getCrypto()
+ );
+ });
}
}
diff --git a/core/Controller/OCSController.php b/core/Controller/OCSController.php
index 27ab9deb08a..c59b0d7ad3f 100644
--- a/core/Controller/OCSController.php
+++ b/core/Controller/OCSController.php
@@ -23,6 +23,7 @@ namespace OC\Core\Controller;
use OC\CapabilitiesManager;
use OC\Security\Bruteforce\Throttler;
+use OC\Security\IdentityProof\Manager;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
use OCP\IUserManager;
@@ -32,13 +33,12 @@ class OCSController extends \OCP\AppFramework\OCSController {
/** @var CapabilitiesManager */
private $capabilitiesManager;
-
/** @var IUserSession */
private $userSession;
-
/** @var IUserManager */
private $userManager;
-
+ /** @var Manager */
+ private $keyManager;
/** @var Throttler */
private $throttler;
@@ -51,19 +51,21 @@ class OCSController extends \OCP\AppFramework\OCSController {
* @param IUserSession $userSession
* @param IUserManager $userManager
* @param Throttler $throttler
+ * @param Manager $keyManager
*/
public function __construct($appName,
IRequest $request,
CapabilitiesManager $capabilitiesManager,
IUserSession $userSession,
IUserManager $userManager,
- Throttler $throttler) {
+ Throttler $throttler,
+ Manager $keyManager) {
parent::__construct($appName, $request);
-
$this->capabilitiesManager = $capabilitiesManager;
$this->userSession = $userSession;
$this->userManager = $userManager;
$this->throttler = $throttler;
+ $this->keyManager = $keyManager;
}
/**
@@ -139,4 +141,24 @@ class OCSController extends \OCP\AppFramework\OCSController {
}
return new DataResponse(null, 101);
}
+
+ /**
+ * @PublicPage
+ *
+ * @param string $cloudId
+ * @return DataResponse
+ */
+ public function getIdentityProof($cloudId) {
+ $userObject = $this->userManager->get($cloudId);
+
+ if($userObject !== null) {
+ $key = $this->keyManager->getKey($userObject);
+ $data = [
+ 'public' => $key->getPublic(),
+ ];
+ return new DataResponse($data);
+ }
+
+ return new DataResponse('User not found', 404);
+ }
}
diff --git a/core/css/apps.css b/core/css/apps.css
index 852879aee89..bef5c7c4937 100644
--- a/core/css/apps.css
+++ b/core/css/apps.css
@@ -289,7 +289,7 @@
border-radius: 3px;
border-top-right-radius: 0;
z-index: 110;
- margin: -5px 14px 5px 10px;
+ margin-top: -5px;
right: 0;
-webkit-filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75));
-moz-filter: drop-shadow(0 0 5px rgba(150, 150, 150, 0.75));
@@ -333,7 +333,8 @@
opacity: .5 !important;
}
.bubble .action:hover,
-.bubble .action:focus {
+.bubble .action:focus,
+.bubble .action.active {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)" !important;
filter: alpha(opacity=100) !important;
opacity: 1 !important;
@@ -646,20 +647,13 @@ em {
}
.popovermenu .menuitem:hover,
-.popovermenu .menuitem:focus {
+.popovermenu .menuitem:focus,
+.popovermenu .menuitem.active {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
filter: alpha(opacity=100);
opacity: 1;
}
-.popovermenu {
- padding: 4px 12px;
-}
-
-.popovermenu li {
- padding: 5px 0;
-}
-
.popovermenu .menuitem img {
padding: initial;
}
@@ -667,8 +661,8 @@ em {
.popovermenu a.menuitem,
.popovermenu label.menuitem,
.popovermenu .menuitem {
- padding: 10px;
- margin: -10px;
+ padding: 10px !important;
+ width: auto;
}
.popovermenu.hidden {
@@ -676,11 +670,10 @@ em {
}
.popovermenu .menuitem {
- display: block;
+ display: flex !important;
line-height: 30px;
- padding-left: 5px;
color: #000;
- padding: 0;
+ align-items: center;
}
.popovermenu .menuitem .icon,
diff --git a/core/css/header.css b/core/css/header.css
index efdd3be7ceb..19ecf77bcd3 100644
--- a/core/css/header.css
+++ b/core/css/header.css
@@ -227,8 +227,8 @@
#apps-management a:hover span,
#apps-management a:focus span,
#apps-management a.active span {
- -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=75)";
- opacity: .75;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+ opacity: 1;
}
#navigation .app-icon {
@@ -372,8 +372,8 @@
#expanddiv a:focus,
#expanddiv a:active,
#expanddiv a.active {
- -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=75)";
- opacity: .75;
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+ opacity: 1;
}
/* do not show display name when profile picture is present */
diff --git a/core/js/sharedialogview.js b/core/js/sharedialogview.js
index 0a29dec73ca..6377d16dd4c 100644
--- a/core/js/sharedialogview.js
+++ b/core/js/sharedialogview.js
@@ -149,6 +149,7 @@
var users = result.ocs.data.exact.users.concat(result.ocs.data.users);
var groups = result.ocs.data.exact.groups.concat(result.ocs.data.groups);
var remotes = result.ocs.data.exact.remotes.concat(result.ocs.data.remotes);
+ var lookup = result.ocs.data.lookup;
if (typeof(result.ocs.data.emails) !== 'undefined') {
var emails = result.ocs.data.exact.emails.concat(result.ocs.data.emails);
} else {
@@ -159,6 +160,7 @@
var groupsLength;
var remotesLength;
var emailsLength;
+ var lookupLength;
var i, j;
@@ -224,7 +226,7 @@
}
}
- var suggestions = users.concat(groups).concat(remotes).concat(emails);
+ var suggestions = users.concat(groups).concat(remotes).concat(emails).concat(lookup);
if (suggestions.length > 0) {
$('.shareWithField').removeClass('error')
diff --git a/core/js/tests/specs/sharedialogviewSpec.js b/core/js/tests/specs/sharedialogviewSpec.js
index 6e86cb8eff7..a44bb309c5c 100644
--- a/core/js/tests/specs/sharedialogviewSpec.js
+++ b/core/js/tests/specs/sharedialogviewSpec.js
@@ -529,7 +529,8 @@ describe('OC.Share.ShareDialogView', function() {
},
'users' : [{'label': 'bob', 'value': {'shareType': 0, 'shareWith': 'test'}}],
'groups' : [],
- 'remotes': []
+ 'remotes': [],
+ 'lookup': []
}
}
});
@@ -577,7 +578,8 @@ describe('OC.Share.ShareDialogView', function() {
}
],
'groups': [],
- 'remotes': []
+ 'remotes': [],
+ 'lookup': []
}
}
});
@@ -635,7 +637,8 @@ describe('OC.Share.ShareDialogView', function() {
}
],
'groups': [],
- 'remotes': []
+ 'remotes': [],
+ 'lookup': []
}
}
});
@@ -715,7 +718,8 @@ describe('OC.Share.ShareDialogView', function() {
}
],
'groups': [],
- 'remotes': []
+ 'remotes': [],
+ 'lookup': []
}
}
});
@@ -765,7 +769,8 @@ describe('OC.Share.ShareDialogView', function() {
}
}
],
- 'remotes': []
+ 'remotes': [],
+ 'lookup': []
}
}
});
@@ -815,7 +820,8 @@ describe('OC.Share.ShareDialogView', function() {
'shareWith': 'foo2@bar.com/baz'
}
}
- ]
+ ],
+ 'lookup': []
}
}
});
diff --git a/core/routes.php b/core/routes.php
index e5636ff6c00..2b8080a3b7b 100644
--- a/core/routes.php
+++ b/core/routes.php
@@ -61,6 +61,7 @@ $application->registerRoutes($this, [
['root' => '/cloud', 'name' => 'OCS#getCurrentUser', 'url' => '/user', 'verb' => 'GET'],
['root' => '', 'name' => 'OCS#getConfig', 'url' => '/config', 'verb' => 'GET'],
['root' => '/person', 'name' => 'OCS#personCheck', 'url' => '/check', 'verb' => 'POST'],
+ ['root' => '/identityproof', 'name' => 'OCS#getIdentityProof', 'url' => '/key/{cloudId}', 'verb' => 'GET'],
],
]);
diff --git a/core/shipped.json b/core/shipped.json
index f831d17f36a..7fb87b7f17d 100644
--- a/core/shipped.json
+++ b/core/shipped.json
@@ -22,6 +22,7 @@
"firstrunwizard",
"gallery",
"logreader",
+ "lookup_server_connector",
"notifications",
"password_policy",
"provisioning_api",
@@ -42,6 +43,7 @@
"files",
"dav",
"federatedfilesharing",
+ "lookup_server_connector",
"provisioning_api",
"twofactor_backupcodes",
"workflowengine"
diff --git a/db_structure.xml b/db_structure.xml
index c7e1e072a8e..545628a9233 100644
--- a/db_structure.xml
+++ b/db_structure.xml
@@ -2113,4 +2113,36 @@
</declaration>
</table>
+ <table>
+
+ <name>*dbprefix*accounts</name>
+
+ <declaration>
+ <field>
+ <name>uid</name>
+ <type>text</type>
+ <default></default>
+ <notnull>true</notnull>
+ <length>64</length>
+ </field>
+ <field>
+ <name>data</name>
+ <type>clob</type>
+ <default></default>
+ <notnull>true</notnull>
+ </field>
+
+ <index>
+ <name>uid_index</name>
+ <primary>true</primary>
+ <unique>true</unique>
+ <field>
+ <name>uid</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+
+ </declaration>
+ </table>
+
</database>
diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php
new file mode 100644
index 00000000000..6496a521326
--- /dev/null
+++ b/lib/private/Accounts/AccountManager.php
@@ -0,0 +1,201 @@
+<?php
+/**
+ * @author Björn Schießle <bjoern@schiessle.org>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @copyright Copyright (c) 2016, Björn Schießle
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace OC\Accounts;
+
+use OCP\IDBConnection;
+use OCP\IUser;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+/**
+ * Class AccountManager
+ *
+ * Manage system accounts table
+ *
+ * @group DB
+ * @package OC\Accounts
+ */
+class AccountManager {
+
+ /** nobody can see my account details */
+ const VISIBILITY_PRIVATE = 'private';
+ /** only contacts, especially trusted servers can see my contact details */
+ const VISIBILITY_CONTACTS_ONLY = 'contacts';
+ /** every body ca see my contact detail, will be published to the lookup server */
+ const VISIBILITY_PUBLIC = 'public';
+
+ const PROPERTY_AVATAR = 'avatar';
+ const PROPERTY_DISPLAYNAME = 'displayname';
+ const PROPERTY_PHONE = 'phone';
+ const PROPERTY_EMAIL = 'email';
+ const PROPERTY_WEBSITE = 'website';
+ const PROPERTY_ADDRESS = 'address';
+ const PROPERTY_TWITTER = 'twitter';
+
+ /** @var IDBConnection database connection */
+ private $connection;
+
+ /** @var string table name */
+ private $table = 'accounts';
+
+ /** @var EventDispatcherInterface */
+ private $eventDispatcher;
+
+ /**
+ * AccountManager constructor.
+ *
+ * @param IDBConnection $connection
+ * @param EventDispatcherInterface $eventDispatcher
+ */
+ public function __construct(IDBConnection $connection, EventDispatcherInterface $eventDispatcher) {
+ $this->connection = $connection;
+ $this->eventDispatcher = $eventDispatcher;
+ }
+
+ /**
+ * update user record
+ *
+ * @param IUser $user
+ * @param $data
+ */
+ public function updateUser(IUser $user, $data) {
+ $userData = $this->getUser($user);
+ if (empty($userData)) {
+ $this->insertNewUser($user, $data);
+ } else {
+ $this->updateExistingUser($user, $data);
+ }
+
+ $this->eventDispatcher->dispatch(
+ 'OC\AccountManager::userUpdated',
+ new GenericEvent($user)
+ );
+ }
+
+ /**
+ * get stored data from a given user
+ *
+ * @param IUser $user
+ * @return array
+ */
+ public function getUser(IUser $user) {
+ $uid = $user->getUID();
+ $query = $this->connection->getQueryBuilder();
+ $query->select('data')->from($this->table)
+ ->where($query->expr()->eq('uid', $query->createParameter('uid')))
+ ->setParameter('uid', $uid);
+ $query->execute();
+ $result = $query->execute()->fetchAll();
+
+ if (empty($result)) {
+ $userData = $this->buildDefaultUserRecord($user);
+ $this->insertNewUser($user, $userData);
+ return $userData;
+ }
+
+ return json_decode($result[0]['data'], true);
+ }
+
+ /**
+ * add new user to accounts table
+ *
+ * @param IUser $user
+ * @param array $data
+ */
+ protected function insertNewUser(IUser $user, $data) {
+ $uid = $user->getUID();
+ $jsonEncodedData = json_encode($data);
+ $query = $this->connection->getQueryBuilder();
+ $query->insert($this->table)
+ ->values(
+ [
+ 'uid' => $query->createNamedParameter($uid),
+ 'data' => $query->createNamedParameter($jsonEncodedData),
+ ]
+ )
+ ->execute();
+ }
+
+ /**
+ * update existing user in accounts table
+ *
+ * @param IUser $user
+ * @param array $data
+ */
+ protected function updateExistingUser(IUser $user, $data) {
+ $uid = $user->getUID();
+ $jsonEncodedData = json_encode($data);
+ $query = $this->connection->getQueryBuilder();
+ $query->update($this->table)
+ ->set('data', $query->createNamedParameter($jsonEncodedData))
+ ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
+ ->execute();
+ }
+
+ /**
+ * build default user record in case not data set exists yet
+ *
+ * @param IUser $user
+ * @return array
+ */
+ protected function buildDefaultUserRecord(IUser $user) {
+ return [
+ self::PROPERTY_DISPLAYNAME =>
+ [
+ 'value' => $user->getDisplayName(),
+ 'scope' => self::VISIBILITY_CONTACTS_ONLY,
+ ],
+ self::PROPERTY_ADDRESS =>
+ [
+ 'value' => '',
+ 'scope' => self::VISIBILITY_PRIVATE,
+ ],
+ self::PROPERTY_WEBSITE =>
+ [
+ 'value' => '',
+ 'scope' => self::VISIBILITY_PRIVATE,
+ ],
+ self::PROPERTY_EMAIL =>
+ [
+ 'value' => $user->getEMailAddress(),
+ 'scope' => self::VISIBILITY_CONTACTS_ONLY,
+ ],
+ self::PROPERTY_AVATAR =>
+ [
+ 'scope' => self::VISIBILITY_CONTACTS_ONLY
+ ],
+ self::PROPERTY_PHONE =>
+ [
+ 'value' => '',
+ 'scope' => self::VISIBILITY_PRIVATE,
+ ],
+ self::PROPERTY_TWITTER =>
+ [
+ 'value' => '',
+ 'scope' => self::VISIBILITY_PRIVATE,
+ ],
+ ];
+ }
+
+}
diff --git a/lib/private/Security/IdentityProof/Key.php b/lib/private/Security/IdentityProof/Key.php
new file mode 100644
index 00000000000..9739a9571bb
--- /dev/null
+++ b/lib/private/Security/IdentityProof/Key.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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\Security\IdentityProof;
+
+class Key {
+ /** @var string */
+ private $publicKey;
+ /** @var string */
+ private $privateKey;
+
+ /**
+ * @param string $publicKey
+ * @param string $privateKey
+ */
+ public function __construct($publicKey, $privateKey) {
+ $this->publicKey = $publicKey;
+ $this->privateKey = $privateKey;
+ }
+
+ public function getPrivate() {
+ return $this->privateKey;
+ }
+
+ public function getPublic() {
+ return $this->publicKey;
+ }
+}
diff --git a/lib/private/Security/IdentityProof/Manager.php b/lib/private/Security/IdentityProof/Manager.php
new file mode 100644
index 00000000000..d2a9e57e338
--- /dev/null
+++ b/lib/private/Security/IdentityProof/Manager.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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\Security\IdentityProof;
+
+use OCP\Files\IAppData;
+use OCP\IUser;
+use OCP\Security\ICrypto;
+
+class Manager {
+ /** @var IAppData */
+ private $appData;
+ /** @var ICrypto */
+ private $crypto;
+
+ /**
+ * @param IAppData $appData
+ * @param ICrypto $crypto
+ */
+ public function __construct(IAppData $appData,
+ ICrypto $crypto) {
+ $this->appData = $appData;
+ $this->crypto = $crypto;
+ }
+
+ /**
+ * Calls the openssl functions to generate a public and private key.
+ * In a separate function for unit testing purposes.
+ *
+ * @return array [$publicKey, $privateKey]
+ */
+ protected function generateKeyPair() {
+ $config = [
+ 'digest_alg' => 'sha512',
+ 'private_key_bits' => 2048,
+ ];
+
+ // Generate new key
+ $res = openssl_pkey_new($config);
+ openssl_pkey_export($res, $privateKey);
+
+ // Extract the public key from $res to $pubKey
+ $publicKey = openssl_pkey_get_details($res);
+ $publicKey = $publicKey['key'];
+
+ return [$publicKey, $privateKey];
+ }
+
+ /**
+ * Generate a key for $user
+ * Note: If a key already exists it will be overwritten
+ *
+ * @param IUser $user
+ * @return Key
+ */
+ protected function generateKey(IUser $user) {
+ list($publicKey, $privateKey) = $this->generateKeyPair();
+
+ // Write the private and public key to the disk
+ try {
+ $this->appData->newFolder($user->getUID());
+ } catch (\Exception $e) {}
+ $folder = $this->appData->getFolder($user->getUID());
+ $folder->newFile('private')
+ ->putContent($this->crypto->encrypt($privateKey));
+ $folder->newFile('public')
+ ->putContent($publicKey);
+
+ return new Key($publicKey, $privateKey);
+ }
+
+ /**
+ * Get public and private key for $user
+ *
+ * @param IUser $user
+ * @return Key
+ */
+ public function getKey(IUser $user) {
+ try {
+ $folder = $this->appData->getFolder($user->getUID());
+ $privateKey = $this->crypto->decrypt(
+ $folder->getFile('private')->getContent()
+ );
+ $publicKey = $folder->getFile('public')->getContent();
+ return new Key($publicKey, $privateKey);
+ } catch (\Exception $e) {
+ return $this->generateKey($user);
+ }
+ }
+}
diff --git a/lib/private/Security/IdentityProof/Signer.php b/lib/private/Security/IdentityProof/Signer.php
new file mode 100644
index 00000000000..50c36b26966
--- /dev/null
+++ b/lib/private/Security/IdentityProof/Signer.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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\Security\IdentityProof;
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IURLGenerator;
+use OCP\IUser;
+use OCP\IUserManager;
+
+class Signer {
+ /** @var Manager */
+ private $keyManager;
+ /** @var ITimeFactory */
+ private $timeFactory;
+ /** @var IURLGenerator */
+ private $urlGenerator;
+ /** @var IUserManager */
+ private $userManager;
+
+ /**
+ * @param Manager $keyManager
+ * @param ITimeFactory $timeFactory
+ * @param IURLGenerator $urlGenerator
+ * @param IUserManager $userManager
+ */
+ public function __construct(Manager $keyManager,
+ ITimeFactory $timeFactory,
+ IURLGenerator $urlGenerator,
+ IUserManager $userManager) {
+ $this->keyManager = $keyManager;
+ $this->timeFactory = $timeFactory;
+ $this->userManager = $userManager;
+ }
+
+ /**
+ * Returns a signed blob for $data
+ *
+ * @param string $type
+ * @param array $data
+ * @param IUser $user
+ * @return array ['message', 'signature']
+ */
+ public function sign($type, array $data, IUser $user) {
+ $privateKey = $this->keyManager->getKey($user)->getPrivate();
+ $data = [
+ 'data' => $data,
+ 'type' => $type,
+ 'signer' => $user->getCloudId(),
+ 'timestamp' => $this->timeFactory->getTime(),
+ ];
+ openssl_sign(json_encode($data), $signature, $privateKey, OPENSSL_ALGO_SHA512);
+
+ return [
+ 'message' => $data,
+ 'signature' => base64_encode($signature),
+ ];
+ }
+
+ /**
+ * @param string $url
+ * @return string
+ */
+ private function removeProtocolFromUrl($url) {
+ if (strpos($url, 'https://') === 0) {
+ return substr($url, strlen('https://'));
+ } else if (strpos($url, 'http://') === 0) {
+ return substr($url, strlen('http://'));
+ }
+
+ return $url;
+ }
+
+ /**
+ * Whether the data is signed properly
+ *
+ * @param array $data
+ * @return bool
+ */
+ public function verify(array $data) {
+ if(isset($data['message'])
+ && isset($data['signature'])
+ && isset($data['message']['signer'])
+ ) {
+ $server = $this->urlGenerator->getAbsoluteURL('/');
+ $postfix = strlen('@' . rtrim($this->removeProtocolFromUrl($server), '/'));
+ $userId = substr($data['message']['signer'], -$postfix);
+
+ $user = $this->userManager->get($userId);
+ if($user !== null) {
+ $key = $this->keyManager->getKey($user);
+ return (bool)openssl_verify(
+ json_encode($data['message']),
+ base64_decode($data['signature']),
+ $key->getPublic()
+ );
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/lib/private/URLGenerator.php b/lib/private/URLGenerator.php
index f041487ea88..c6f1ebd4594 100644
--- a/lib/private/URLGenerator.php
+++ b/lib/private/URLGenerator.php
@@ -158,11 +158,12 @@ class URLGenerator implements IURLGenerator {
// Check if the app is in the app folder
$path = '';
- if(\OCP\App::isEnabled('theming') && $image === "favicon.ico" && \OC::$server->getThemingDefaults()->shouldReplaceIcons()) {
+ $themingEnabled = $this->config->getSystemValue('installed', false) && \OCP\App::isEnabled('theming');
+ if($themingEnabled && $image === "favicon.ico" && \OC::$server->getThemingDefaults()->shouldReplaceIcons()) {
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
if($app==="") { $app = "core"; }
$path = $this->linkToRoute('theming.Icon.getFavicon', [ 'app' => $app ]) . '?v='. $cacheBusterValue;
- } elseif(\OCP\App::isEnabled('theming') && $image === "favicon-touch.png" && \OC::$server->getThemingDefaults()->shouldReplaceIcons()) {
+ } elseif($themingEnabled && $image === "favicon-touch.png" && \OC::$server->getThemingDefaults()->shouldReplaceIcons()) {
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
if($app==="") { $app = "core"; }
$path = $this->linkToRoute('theming.Icon.getTouchIcon', [ 'app' => $app ]) . '?v='. $cacheBusterValue;
diff --git a/lib/private/Updater.php b/lib/private/Updater.php
index 2fed67988b8..c3d8ef9fea3 100644
--- a/lib/private/Updater.php
+++ b/lib/private/Updater.php
@@ -53,7 +53,7 @@ class Updater extends BasicEmitter {
/** @var ILogger $log */
private $log;
-
+
/** @var IConfig */
private $config;
diff --git a/settings/Application.php b/settings/Application.php
index d907cd666fb..ca2b2b91f71 100644
--- a/settings/Application.php
+++ b/settings/Application.php
@@ -34,9 +34,21 @@ use OC\App\AppStore\Fetcher\AppFetcher;
use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\AppFramework\Utility\TimeFactory;
use OC\Authentication\Token\IProvider;
+use OC\Files\View;
use OC\Server;
+use OC\Settings\Controller\AppSettingsController;
+use OC\Settings\Controller\AuthSettingsController;
+use OC\Settings\Controller\CertificateController;
+use OC\Settings\Controller\CheckSetupController;
+use OC\Settings\Controller\EncryptionController;
+use OC\Settings\Controller\GroupsController;
+use OC\Settings\Controller\LogSettingsController;
+use OC\Settings\Controller\MailSettingsController;
+use OC\Settings\Controller\SecuritySettingsController;
+use OC\Settings\Controller\UsersController;
use OC\Settings\Middleware\SubadminMiddleware;
use OCP\AppFramework\App;
+use OCP\AppFramework\IAppContainer;
use OCP\IContainer;
use OCP\Settings\IManager;
use OCP\Util;
@@ -57,7 +69,129 @@ class Application extends App {
// Register Middleware
$container->registerAlias('SubadminMiddleware', SubadminMiddleware::class);
- $container->registerMiddleWare('SubadminMiddleware');
+ /**
+ * Controllers
+ */
+ $container->registerService('MailSettingsController', function(IContainer $c) {
+ return new MailSettingsController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('L10N'),
+ $c->query('Config'),
+ $c->query('UserSession'),
+ $c->query('Defaults'),
+ $c->query('Mailer'),
+ $c->query('DefaultMailAddress')
+ );
+ });
+ $container->registerService('EncryptionController', function(IContainer $c) {
+ return new EncryptionController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('L10N'),
+ $c->query('Config'),
+ $c->query('DatabaseConnection'),
+ $c->query('UserManager'),
+ new View(),
+ $c->query('Logger')
+ );
+ });
+
+ $container->registerService('AppSettingsController', function(IContainer $c) {
+ return new AppSettingsController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('L10N'),
+ $c->query('Config'),
+ $c->query('INavigationManager'),
+ $c->query('IAppManager'),
+ $c->query('CategoryFetcher'),
+ $c->query('AppFetcher'),
+ \OC::$server->getL10NFactory()
+ );
+ });
+ $container->registerService('AuthSettingsController', function(IContainer $c) {
+ return new AuthSettingsController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('ServerContainer')->query('OC\Authentication\Token\IProvider'),
+ $c->query('UserManager'),
+ $c->query('ServerContainer')->getSession(),
+ $c->query('ServerContainer')->getSecureRandom(),
+ $c->query('UserId')
+ );
+ });
+ $container->registerService('SecuritySettingsController', function(IContainer $c) {
+ return new SecuritySettingsController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('Config')
+ );
+ });
+ $container->registerService('AccountManager', function(IAppContainer $c) {
+ return new AccountManager($c->getServer()->getDatabaseConnection());
+ });
+ $container->registerService('CertificateController', function(IContainer $c) {
+ return new CertificateController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('CertificateManager'),
+ $c->query('SystemCertificateManager'),
+ $c->query('L10N'),
+ $c->query('IAppManager')
+ );
+ });
+ $container->registerService('GroupsController', function(IContainer $c) {
+ return new GroupsController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('GroupManager'),
+ $c->query('UserSession'),
+ $c->query('IsAdmin'),
+ $c->query('L10N')
+ );
+ });
+ $container->registerService('UsersController', function(IContainer $c) {
+ return new UsersController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('UserManager'),
+ $c->query('GroupManager'),
+ $c->query('UserSession'),
+ $c->query('Config'),
+ $c->query('IsAdmin'),
+ $c->query('L10N'),
+ $c->query('Logger'),
+ $c->query('Defaults'),
+ $c->query('Mailer'),
+ $c->query('DefaultMailAddress'),
+ $c->query('URLGenerator'),
+ $c->query('OCP\\App\\IAppManager'),
+ $c->query('OCP\\IAvatarManager'),
+ $c->query('AccountManager')
+ );
+ });
+ $container->registerService('LogSettingsController', function(IContainer $c) {
+ return new LogSettingsController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('Config'),
+ $c->query('L10N')
+ );
+ });
+ $container->registerService('CheckSetupController', function(IContainer $c) {
+ return new CheckSetupController(
+ $c->query('AppName'),
+ $c->query('Request'),
+ $c->query('Config'),
+ $c->query('ClientService'),
+ $c->query('URLGenerator'),
+ $c->query('Util'),
+ $c->query('L10N'),
+ $c->query('Checker')
+ );
+ });
+
/**
* Core class wrappers
diff --git a/settings/Controller/UsersController.php b/settings/Controller/UsersController.php
index 89831a66aba..206f1872542 100644
--- a/settings/Controller/UsersController.php
+++ b/settings/Controller/UsersController.php
@@ -30,7 +30,9 @@
namespace OC\Settings\Controller;
+use OC\Accounts\AccountManager;
use OC\AppFramework\Http;
+use OC\ForbiddenException;
use OC\User\User;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
@@ -47,6 +49,7 @@ use OCP\IUserManager;
use OCP\IUserSession;
use OCP\Mail\IMailer;
use OCP\IAvatarManager;
+use Punic\Exception;
/**
* @package OC\Settings\Controller
@@ -80,6 +83,8 @@ class UsersController extends Controller {
private $isRestoreEnabled = false;
/** @var IAvatarManager */
private $avatarManager;
+ /** @var AccountManager */
+ private $accountManager;
/**
* @param string $appName
@@ -97,6 +102,7 @@ class UsersController extends Controller {
* @param IURLGenerator $urlGenerator
* @param IAppManager $appManager
* @param IAvatarManager $avatarManager
+ * @param AccountManager $accountManager
*/
public function __construct($appName,
IRequest $request,
@@ -112,7 +118,9 @@ class UsersController extends Controller {
$fromMailAddress,
IURLGenerator $urlGenerator,
IAppManager $appManager,
- IAvatarManager $avatarManager) {
+ IAvatarManager $avatarManager,
+ AccountManager $accountManager
+) {
parent::__construct($appName, $request);
$this->userManager = $userManager;
$this->groupManager = $groupManager;
@@ -126,6 +134,7 @@ class UsersController extends Controller {
$this->fromMailAddress = $fromMailAddress;
$this->urlGenerator = $urlGenerator;
$this->avatarManager = $avatarManager;
+ $this->accountManager = $accountManager;
// check for encryption state - TODO see formatUserForIndex
$this->isEncryptionAppEnabled = $appManager->isEnabledForUser('encryption');
@@ -493,35 +502,42 @@ class UsersController extends Controller {
}
/**
- * Set the mail address of a user
- *
* @NoAdminRequired
* @NoSubadminRequired
* @PasswordConfirmationRequired
*
- * @param string $id
- * @param string $mailAddress
+ * @param string $avatarScope
+ * @param string $displayname
+ * @param string $displaynameScope
+ * @param string $phone
+ * @param string $phoneScope
+ * @param string $email
+ * @param string $emailScope
+ * @param string $website
+ * @param string $websiteScope
+ * @param string $address
+ * @param string $addressScope
+ * @param string $twitter
+ * @param string $twitterScope
* @return DataResponse
*/
- public function setMailAddress($id, $mailAddress) {
- $userId = $this->userSession->getUser()->getUID();
- $user = $this->userManager->get($id);
-
- if($userId !== $id
- && !$this->isAdmin
- && !$this->groupManager->getSubAdmin()->isUserAccessible($this->userSession->getUser(), $user)) {
- return new DataResponse(
- array(
- 'status' => 'error',
- 'data' => array(
- 'message' => (string)$this->l10n->t('Forbidden')
- )
- ),
- Http::STATUS_FORBIDDEN
- );
- }
-
- if($mailAddress !== '' && !$this->mailer->validateMailAddress($mailAddress)) {
+ public function setUserSettings($avatarScope,
+ $displayname,
+ $displaynameScope,
+ $phone,
+ $phoneScope,
+ $email,
+ $emailScope,
+ $website,
+ $websiteScope,
+ $address,
+ $addressScope,
+ $twitter,
+ $twitterScope
+ ) {
+
+
+ if(!empty($email) && !$this->mailer->validateMailAddress($email)) {
return new DataResponse(
array(
'status' => 'error',
@@ -533,46 +549,80 @@ class UsersController extends Controller {
);
}
- if(!$user){
+ $user = $this->userSession->getUser();
+
+ $data = [
+ AccountManager::PROPERTY_AVATAR => ['scope' => $avatarScope],
+ AccountManager::PROPERTY_DISPLAYNAME => ['value' => $displayname, 'scope' => $displaynameScope],
+ AccountManager::PROPERTY_EMAIL=> ['value' => $email, 'scope' => $emailScope],
+ AccountManager::PROPERTY_WEBSITE => ['value' => $website, 'scope' => $websiteScope],
+ AccountManager::PROPERTY_ADDRESS => ['value' => $address, 'scope' => $addressScope],
+ AccountManager::PROPERTY_PHONE => ['value' => $phone, 'scope' => $phoneScope],
+ AccountManager::PROPERTY_TWITTER => ['value' => $twitter, 'scope' => $twitterScope]
+ ];
+
+ $this->accountManager->updateUser($user, $data);
+
+ try {
+ $this->saveUserSettings($user, $data);
return new DataResponse(
array(
- 'status' => 'error',
+ 'status' => 'success',
'data' => array(
- 'message' => (string)$this->l10n->t('Invalid user')
+ 'userId' => $user->getUID(),
+ 'avatarScope' => $avatarScope,
+ 'displayname' => $displayname,
+ 'displaynameScope' => $displaynameScope,
+ 'email' => $email,
+ 'emailScope' => $emailScope,
+ 'website' => $website,
+ 'websiteScope' => $websiteScope,
+ 'address' => $address,
+ 'addressScope' => $addressScope,
+ 'message' => (string)$this->l10n->t('Settings saved')
)
),
- Http::STATUS_UNPROCESSABLE_ENTITY
+ Http::STATUS_OK
);
+ } catch (ForbiddenException $e) {
+ return new DataResponse([
+ 'status' => 'error',
+ 'data' => [
+ 'message' => $e->getMessage()
+ ],
+ ]);
}
- // this is the only permission a backend provides and is also used
- // for the permission of setting a email address
- if(!$user->canChangeDisplayName()){
- return new DataResponse(
- array(
- 'status' => 'error',
- 'data' => array(
- 'message' => (string)$this->l10n->t('Unable to change mail address')
- )
- ),
- Http::STATUS_FORBIDDEN
- );
+ }
+
+
+ /**
+ * update account manager with new user data
+ *
+ * @param IUser $user
+ * @param array $data
+ * @throws ForbiddenException
+ */
+ private function saveUserSettings(IUser $user, $data) {
+
+ // keep the user back-end up-to-date with the latest display name and email
+ // address
+ $oldDisplayName = $user->getDisplayName();
+ if (isset($data[AccountManager::PROPERTY_DISPLAYNAME]['value']) && $oldDisplayName !== $data[AccountManager::PROPERTY_DISPLAYNAME]['value']) {
+ $result = $user->setDisplayName($data[AccountManager::PROPERTY_DISPLAYNAME]['value']);
+ if ($result === false) {
+ throw new ForbiddenException($this->l10n->t('Unable to change full name'));
+ }
}
- // delete user value if email address is empty
- $user->setEMailAddress($mailAddress);
+ if (isset($data['email'][0]['value']) && $user->getEMailAddress() !== $data['email'][0]['value']) {
+ $result = $user->setEMailAddress($data['email'][0]['value']);
+ if ($result === false) {
+ throw new ForbiddenException($this->l10n->t('Unable to change mail address'));
+ }
+ }
- return new DataResponse(
- array(
- 'status' => 'success',
- 'data' => array(
- 'username' => $id,
- 'mailAddress' => $mailAddress,
- 'message' => (string)$this->l10n->t('Email saved')
- )
- ),
- Http::STATUS_OK
- );
+ $this->accountManager->updateUser($user, $data);
}
/**
@@ -619,6 +669,7 @@ class UsersController extends Controller {
* @NoAdminRequired
* @NoSubadminRequired
* @PasswordConfirmationRequired
+ * @todo merge into saveUserSettings
*
* @param string $username
* @param string $displayName
@@ -626,11 +677,6 @@ class UsersController extends Controller {
*/
public function setDisplayName($username, $displayName) {
$currentUser = $this->userSession->getUser();
-
- if ($username === null) {
- $username = $currentUser->getUID();
- }
-
$user = $this->userManager->get($username);
if ($user === null ||
@@ -638,8 +684,10 @@ class UsersController extends Controller {
(
!$this->groupManager->isAdmin($currentUser->getUID()) &&
!$this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $user) &&
- $currentUser !== $user)
- ) {
+ $currentUser->getUID() !== $username
+
+ )
+ ) {
return new DataResponse([
'status' => 'error',
'data' => [
@@ -648,7 +696,12 @@ class UsersController extends Controller {
]);
}
- if ($user->setDisplayName($displayName)) {
+ $userData = $this->accountManager->getUser($user);
+ $userData[AccountManager::PROPERTY_DISPLAYNAME]['value'] = $displayName;
+
+
+ try {
+ $this->saveUserSettings($user, $userData);
return new DataResponse([
'status' => 'success',
'data' => [
@@ -657,11 +710,11 @@ class UsersController extends Controller {
'displayName' => $displayName,
],
]);
- } else {
+ } catch (ForbiddenException $e) {
return new DataResponse([
'status' => 'error',
'data' => [
- 'message' => $this->l10n->t('Unable to change full name'),
+ 'message' => $e->getMessage(),
'displayName' => $user->getDisplayName(),
],
]);
diff --git a/settings/css/settings.css b/settings/css/settings.css
index 9008ba5a985..23d6cd98007 100644
--- a/settings/css/settings.css
+++ b/settings/css/settings.css
@@ -7,16 +7,14 @@ input#openid, input#webdav { width:20em; }
/* PERSONAL */
-#avatar {
- display: inline-block;
- float: left;
+#avatarform {
width: 160px;
padding-right: 0;
}
-#avatar .avatardiv {
+#avatarform .avatardiv {
margin-bottom: 10px;
}
-#avatar .warning {
+#avatarform .warning {
width: 100%;
}
#uploadavatarbutton,
@@ -28,7 +26,7 @@ input#openid, input#webdav { width:20em; }
.jcrop-holder {
z-index: 500;
}
-#avatar #cropper {
+#cropper {
float: left;
z-index: 500;
/* float cropper above settings page to prevent unexpected flowing from dynamically sized element */
@@ -40,7 +38,7 @@ input#openid, input#webdav { width:20em; }
width: 100%;
height: calc(100% - 45px);
}
-#avatar #cropper .inner-container {
+#cropper .inner-container {
z-index: 2001; /* above the top bar if needed */
position: absolute;
top: 50%;
@@ -53,17 +51,80 @@ input#openid, input#webdav { width:20em; }
padding: 15px;
}
-#avatar #cropper .inner-container .jcrop-holder {
+#cropper .inner-container .jcrop-holder {
box-shadow: 0 0 7px #888;
}
-#avatar #cropper .inner-container .button {
+#cropper .inner-container .button {
margin-top: 15px;
}
-#avatar #cropper .inner-container .primary {
+#cropper .inner-container .primary {
float: right;
}
-#displaynameform,
+#personal-settings-avatar-container {
+ float: left;
+}
+#personal-settings-container {
+ position: relative;
+ float: left;
+ min-width: 280px;
+ max-width: 700px;
+ width: calc(100% - 200px);
+}
+#personal-settings-container:after {
+ clear: both;
+}
+#personal-settings-container > div {
+ float: left;
+ height: 100px;
+ min-width: 300px;
+}
+#personal-settings-container.no-edit > div {
+ height: 20px;
+ min-width: 200px;
+}
+#avatarform > h2,
+#personal-settings-container > div h2 {
+ position: relative;
+}
+#personal-settings-container > div h2 span[class^="icon-"],
+#personal-settings-avatar-container h2 span[class^="icon-"] {
+ display: inline-block;
+ padding: 8px;
+ margin-left: -8px;
+ margin-bottom: -10px;
+ background-size: 16px;
+ opacity: .3;
+ cursor: pointer;
+}
+.personal-settings-setting-box input[type="text"],
+.personal-settings-setting-box input[type="email"],
+.personal-settings-setting-box input[type="tel"] {
+ width: 17em;
+}
+#personal-settings-container > div > form span[class^="icon-checkmark"] {
+ position: absolute;
+ left: 239px;
+ top: 82px;
+ pointer-events: none;
+}
+.federationScopeMenu {
+ top: 44px;
+}
+.federationScopeMenu.bubble::after {
+ right: 50%;
+ transform: translate(50%, 0);
+}
+.federationScopeMenu.popovermenu a.menuitem,
+.federationScopeMenu.popovermenu label.menuitem,
+.federationScopeMenu.popovermenu .menuitem {
+ font-size: 12.8px;
+ line-height: 1.6em;
+}
+.federationScopeMenu.popovermenu .menuitem .menuitem-text-detail {
+ opacity: .75;
+}
+
#lostpassword,
#groups,
#passwordform {
@@ -73,7 +134,7 @@ input#openid, input#webdav { width:20em; }
padding-right: 0;
min-width: 60%;
}
-#avatar,
+#avatarform,
#passwordform {
margin-bottom: 0;
padding-bottom: 0;
@@ -81,6 +142,8 @@ input#openid, input#webdav { width:20em; }
#groups {
overflow-wrap: break-word;
max-width: 75%;
+ display: block;;
+ clear: both;
}
.clientsbox img {
@@ -104,10 +167,6 @@ input#openid, input#webdav { width:20em; }
input#identity {
width: 20em;
}
-#displayName,
-#email {
- width: 17em;
-}
#showWizard {
display: inline-block;
diff --git a/settings/js/federationscopemenu.js b/settings/js/federationscopemenu.js
new file mode 100644
index 00000000000..066f648c104
--- /dev/null
+++ b/settings/js/federationscopemenu.js
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2016
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+/* global OC, Handlebars */
+(function() {
+
+ var TEMPLATE_MENU =
+ '<ul>' +
+ '{{#each items}}' +
+ '<li>' +
+ '<a href="#" class="menuitem action action-{{name}} permanent {{#if active}}active{{/if}}" data-action="{{name}}">' +
+ '{{#if icon}}<img class="icon" src="{{icon}}"/>' +
+ '{{else}}'+
+ '{{#if iconClass}}' +
+ '<span class="icon {{iconClass}}"></span>' +
+ '{{else}}' +
+ '<span class="no-icon"></span>' +
+ '{{/if}}' +
+ '{{/if}}' +
+ '<p><strong class="menuitem-text">{{displayName}}</strong><br>' +
+ '<span class="menuitem-text-detail">{{tooltip}}</span></p></a>' +
+ '</li>' +
+ '{{/each}}' +
+ '</ul>';
+
+ /**
+ * Construct a new FederationScopeMenu instance
+ * @constructs FederationScopeMenu
+ * @memberof OC.Settings
+ */
+ var FederationScopeMenu = OC.Backbone.View.extend({
+ tagName: 'div',
+ className: 'federationScopeMenu popovermenu bubble hidden open menu',
+ field: undefined,
+ _scopes: undefined,
+
+ initialize: function(options) {
+ this.field = options.field;
+ this._scopes = [
+ {
+ name: 'private',
+ displayName: (this.field === 'avatar' || this.field === 'displayname') ? t('core', 'Local') : t('core', 'Private'),
+ tooltip: (this.field === 'avatar' || this.field === 'displayname') ? t('core', 'Only visible to local users') : t('core', 'Only visible to you'),
+ icon: OC.imagePath('core', 'actions/password'),
+ active: false
+ },
+ {
+ name: 'contacts',
+ displayName: t('core', 'Contacts'),
+ tooltip: t('core', 'Visible to local users and to trusted servers'),
+ icon: OC.imagePath('core', 'places/contacts-dark'),
+ active: false
+ },
+ {
+ name: 'public',
+ displayName: t('core', 'Public'),
+ tooltip: t('core', 'Will be synced to a global and public address book'),
+ icon: OC.imagePath('core', 'places/link'),
+ active: false
+ }
+ ];
+ },
+
+ /**
+ * Current context
+ *
+ * @type OCA.Files.FileActionContext
+ */
+ _context: null,
+
+ events: {
+ 'click a.action': '_onClickAction'
+ },
+
+ template: Handlebars.compile(TEMPLATE_MENU),
+
+ /**
+ * Event handler whenever an action has been clicked within the menu
+ *
+ * @param {Object} event event object
+ */
+ _onClickAction: function(event) {
+ var $target = $(event.currentTarget);
+ if (!$target.hasClass('menuitem')) {
+ $target = $target.closest('.menuitem');
+ }
+
+ this.trigger('select:scope', $target.data('action'));
+
+ OC.hideMenus();
+ },
+
+ /**
+ * Renders the menu with the currently set items
+ */
+ render: function() {
+ this.$el.html(this.template({
+ items: this._scopes
+ }));
+ },
+
+ /**
+ * Displays the menu
+ */
+ show: function(context) {
+ this._context = context;
+ var currentlyActiveValue = $('#'+context.target.closest('form').id).find('.icon-checkmark > input')[0].value;
+
+ for(var i = 0 in this._scopes) {
+ this._scopes[i].active = false;
+ }
+
+ switch (currentlyActiveValue) {
+ case "private":
+ this._scopes[0].active = true;
+ break;
+ case "contacts":
+ this._scopes[1].active = true;
+ break;
+ case "public":
+ this._scopes[2].active = true;
+ break;
+ }
+
+ var $el = $(context.target);
+ var offsetIcon = $el.offset();
+ var offsetHeading = $el.closest('h2').offset();
+
+ this.render();
+ this.$el.removeClass('hidden');
+
+ OC.showMenu(null, this.$el);
+
+ //Set the menuwidth
+ var menuWidth = this.$el.width();
+ this.$el.css('width', menuWidth);
+
+ //Calculate menu position
+ var l = offsetIcon.left - offsetHeading.left;
+ l = l - (menuWidth / 2) + ($el.outerWidth()/2);
+ this.$el.css('left', l);
+
+ }
+ });
+
+ OC.Settings = OC.Settings || {};
+ OC.Settings.FederationScopeMenu = FederationScopeMenu;
+
+})();
diff --git a/settings/js/federationsettingsview.js b/settings/js/federationsettingsview.js
new file mode 100644
index 00000000000..7a629fc2ab6
--- /dev/null
+++ b/settings/js/federationsettingsview.js
@@ -0,0 +1,176 @@
+/* global OC, result, _ */
+
+/**
+ * Copyright (c) 2016, Christoph Wurst <christoph@owncloud.com>
+ *
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+(function(_, $, OC) {
+ 'use strict';
+
+ var FederationSettingsView = OC.Backbone.View.extend({
+ _inputFields: undefined,
+
+ /** @var Backbone.Model */
+ _config: undefined,
+
+ initialize: function(options) {
+ options = options || {};
+
+ if (options.config) {
+ this._config = options.config;
+ } else {
+ this._config = new OC.Settings.UserSettings();
+ }
+
+ this._inputFields = [
+ 'displayname',
+ 'phone',
+ 'email',
+ 'website',
+ 'twitter',
+ 'address',
+ 'avatar'
+ ];
+
+ var self = this;
+ _.each(this._inputFields, function(field) {
+ var scopeOnly = field === 'avatar';
+
+ // Initialize config model
+ if (!scopeOnly) {
+ self._config.set(field, $('#' + field).val());
+ }
+ self._config.set(field + 'Scope', $('#' + field + 'scope').val());
+
+ // Set inputs whenever model values change
+ if (!scopeOnly) {
+ self.listenTo(self._config, 'change:' + field, function() {
+ self.$('#' + field).val(self._config.get(field));
+ });
+ }
+ self.listenTo(self._config, 'change:' + field + 'Scope', function() {
+ self._setFieldScopeIcon(field, self._config.get(field + 'Scope'));
+ });
+ });
+
+ this._registerEvents();
+ },
+
+ render: function() {
+ var self = this;
+ _.each(this._inputFields, function(field) {
+ var $heading = self.$('#' + field + 'form h2');
+ var $icon = self.$('#' + field + 'form h2 > span');
+ var scopeMenu = new OC.Settings.FederationScopeMenu({field: field});
+
+ self.listenTo(scopeMenu, 'select:scope', function(scope) {
+ self._onScopeChanged(field, scope);
+ });
+ $heading.append(scopeMenu.$el);
+ $icon.on('click', _.bind(scopeMenu.show, scopeMenu));
+
+ // Restore initial state
+ self._setFieldScopeIcon(field, self._config.get(field + 'Scope'));
+ });
+ },
+
+ _registerEvents: function() {
+ var self = this;
+ _.each(this._inputFields, function(field) {
+ if (field === 'avatar') {
+ return;
+ }
+ self.$('#' + field).keyUpDelayedOrEnter(_.bind(self._onInputChanged, self));
+ });
+ },
+
+ _onInputChanged: function(e) {
+ var self = this;
+
+ var $dialog = $('.oc-dialog:visible');
+ if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
+ if($dialog.length === 0) {
+ OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._onInputChanged, this, e));
+ }
+ return;
+ }
+ var $target = $(e.target);
+ var value = $target.val();
+ var field = $target.attr('id');
+ this._config.set(field, value);
+
+ var savingData = this._config.save({
+ error: function(jqXHR) {
+ OC.msg.finishedSaving('#personal-settings-container .msg', jqXHR);
+ }
+ });
+
+ $.when(savingData).done(function() {
+ //OC.msg.finishedSaving('#personal-settings-container .msg', result)
+ self._showInputChangeSuccess(field);
+ if (field === 'displayname') {
+ self._updateDisplayName(value);
+ }
+ });
+ },
+
+ _updateDisplayName: function(displayName) {
+ // update displayName on the top right expand button
+ $('#expandDisplayName').text(displayName);
+ // update avatar if avatar is available
+ if (!$('#removeavatar').hasClass('hidden')) {
+ updateAvatar();
+ }
+ },
+
+ _onScopeChanged: function(field, scope) {
+ var $dialog = $('.oc-dialog:visible');
+ if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
+ if($dialog.length === 0) {
+ OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this._onScopeChanged, this, field, scope));
+ }
+ return;
+ }
+
+ this._config.set(field + 'Scope', scope);
+
+ $('#' + field).parent().find('span > input').val(scope);
+
+ // TODO: user loading/success feedback
+ this._config.save();
+ this._setFieldScopeIcon(field, scope);
+ },
+
+ _showInputChangeSuccess: function(field) {
+ var $icon = this.$('#' + field + 'form > span');
+ $icon.fadeIn(200);
+ setTimeout(function() {
+ $icon.fadeOut(300);
+ }, 2000);
+ },
+
+ _setFieldScopeIcon: function(field, scope) {
+ var $icon = this.$('#' + field + 'form > h2 > span');
+ $icon.removeClass('icon-password');
+ $icon.removeClass('icon-contacts-dark');
+ $icon.removeClass('icon-link');
+ switch (scope) {
+ case 'private':
+ $icon.addClass('icon-password');
+ break;
+ case 'contacts':
+ $icon.addClass('icon-contacts-dark');
+ break;
+ case 'public':
+ $icon.addClass('icon-link');
+ break;
+ }
+ }
+ });
+
+ OC.Settings = OC.Settings || {};
+ OC.Settings.FederationSettingsView = FederationSettingsView;
+})(_, $, OC);
diff --git a/settings/js/personal.js b/settings/js/personal.js
index c2cb437bd13..9045851ba0c 100644
--- a/settings/js/personal.js
+++ b/settings/js/personal.js
@@ -1,10 +1,15 @@
+/* global OC */
+
/**
* Copyright (c) 2011, Robin Appelman <icewind1991@gmail.com>
* 2013, Morris Jobke <morris.jobke@gmail.com>
+ * 2016, Christoph Wurst <christoph@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or later.
* See the COPYING-README file.
*/
+OC.Settings = OC.Settings || {};
+
/**
* The callback will be fired as soon as enter is pressed by the
* user or 1 second after the last data entry
@@ -21,21 +26,21 @@ jQuery.fn.keyUpDelayedOrEnter = function (callback, allowEmptyValue) {
return;
}
if (allowEmptyValue || that.val() !== '') {
- cb();
+ cb(event);
}
}, 1000));
this.keypress(function (event) {
if (event.keyCode === 13 && (allowEmptyValue || that.val() !== '')) {
event.preventDefault();
- cb();
+ cb(event);
}
});
- this.bind('paste', null, function (e) {
- if(!e.keyCode){
+ this.bind('paste', null, function (event) {
+ if(!event.keyCode){
if (allowEmptyValue || that.val() !== '') {
- cb();
+ cb(event);
}
}
});
@@ -187,7 +192,7 @@ function avatarResponseHandler (data) {
if (typeof data === 'string') {
data = JSON.parse(data);
}
- var $warning = $('#avatar .warning');
+ var $warning = $('#avatarform .warning');
$warning.hide();
if (data.status === "success") {
updateAvatar();
@@ -265,8 +270,10 @@ $(document).ready(function () {
}
});
- $('#displayName').keyUpDelayedOrEnter(changeDisplayName);
- $('#email').keyUpDelayedOrEnter(changeEmailAddress, true);
+ var federationSettingsView = new OC.Settings.FederationSettingsView({
+ el: '#personal-settings'
+ });
+ federationSettingsView.render();
$("#languageinput").change(function () {
// Serialize the data
@@ -405,7 +412,7 @@ $(document).ready(function () {
// Load the big avatar
if (oc_config.enable_avatars) {
- $('#avatar .avatardiv').avatar(OC.currentUser, 145);
+ $('#avatarform .avatardiv').avatar(OC.currentUser, 145);
}
// Show token views
@@ -452,3 +459,5 @@ OC.Encryption.msg = {
}
}
};
+
+OC.Settings.updateAvatar = updateAvatar;
diff --git a/settings/js/usersettings.js b/settings/js/usersettings.js
new file mode 100644
index 00000000000..fcfe556b1d9
--- /dev/null
+++ b/settings/js/usersettings.js
@@ -0,0 +1,50 @@
+/* global OC */
+
+/**
+ * Copyright (c) 2016, Christoph Wurst <christoph@owncloud.com>
+ *
+ * This file is licensed under the Affero General Public License version 3 or later.
+ * See the COPYING-README file.
+ */
+
+(function() {
+ 'use strict';
+
+ /**
+ * Model for storing and saving user settings
+ *
+ * @class UserSettings
+ */
+ var UserSettings = OC.Backbone.Model.extend({
+ url: OC.generateUrl('/settings/users/{id}/settings', {id: OC.currentUser}),
+ isNew: function() {
+ return false; // Force PUT on .save()
+ },
+ parse: function(data) {
+ if (_.isUndefined(data)) {
+ return null;
+ }
+ if (_.isUndefined(data.data)) {
+ return null;
+ }
+ data = data.data;
+
+ var ignored = [
+ 'userId',
+ 'message'
+ ];
+
+ _.each(ignored, function(ign) {
+ if (!_.isUndefined(data[ign])) {
+ delete data[ign];
+ }
+ });
+
+ return data;
+ }
+ });
+
+ OC.Settings = OC.Settings || {};
+
+ OC.Settings.UserSettings = UserSettings;
+})(); \ No newline at end of file
diff --git a/settings/personal.php b/settings/personal.php
index 01c358de3ae..24955c94071 100644
--- a/settings/personal.php
+++ b/settings/personal.php
@@ -40,6 +40,7 @@ OC_Util::checkLoggedIn();
$defaults = \OC::$server->getThemingDefaults();
$certificateManager = \OC::$server->getCertificateManager();
+$accountManager = new \OC\Accounts\AccountManager(\OC::$server->getDatabaseConnection(), \OC::$server->getEventDispatcher());
$config = \OC::$server->getConfig();
$urlGenerator = \OC::$server->getURLGenerator();
@@ -47,7 +48,10 @@ $urlGenerator = \OC::$server->getURLGenerator();
OC_Util::addScript('settings', 'authtoken');
OC_Util::addScript('settings', 'authtoken_collection');
OC_Util::addScript('settings', 'authtoken_view');
-OC_Util::addScript( 'settings', 'personal' );
+OC_Util::addScript('settings', 'usersettings');
+OC_Util::addScript('settings', 'federationsettingsview');
+OC_Util::addScript('settings', 'federationscopemenu');
+OC_Util::addScript('settings', 'personal');
OC_Util::addScript('settings', 'certificates');
OC_Util::addStyle( 'settings', 'settings' );
\OC_Util::addVendorScript('strengthify/jquery.strengthify');
@@ -66,7 +70,6 @@ OC::$server->getNavigationManager()->setActiveEntry('personal');
$storageInfo=OC_Helper::getStorageInfo('/');
$user = OC::$server->getUserManager()->get(OC_User::getUser());
-$email = $user->getEMailAddress();
$userLang=$config->getUserValue( OC_User::getUser(), 'core', 'lang', \OC::$server->getL10NFactory()->findLanguage() );
$languageCodes = \OC::$server->getL10NFactory()->findAvailableLanguages();
@@ -152,16 +155,34 @@ if ($storageInfo['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED) {
} else {
$totalSpace = OC_Helper::humanFileSize($storageInfo['total']);
}
+
+$uid = $user->getUID();
+$userData = $accountManager->getUser($user);
+
$tmpl->assign('total_space', $totalSpace);
$tmpl->assign('usage_relative', $storageInfo['relative']);
$tmpl->assign('clients', $clients);
-$tmpl->assign('email', $email);
+$tmpl->assign('email', $userData[\OC\Accounts\AccountManager::PROPERTY_EMAIL]['value']);
$tmpl->assign('languages', $languages);
$tmpl->assign('commonlanguages', $commonLanguages);
$tmpl->assign('activelanguage', $userLang);
$tmpl->assign('passwordChangeSupported', OC_User::canUserChangePassword(OC_User::getUser()));
$tmpl->assign('displayNameChangeSupported', OC_User::canUserChangeDisplayName(OC_User::getUser()));
-$tmpl->assign('displayName', OC_User::getDisplayName());
+$tmpl->assign('displayName', $userData[\OC\Accounts\AccountManager::PROPERTY_DISPLAYNAME]['value']);
+
+$tmpl->assign('phone', $userData[\OC\Accounts\AccountManager::PROPERTY_PHONE]['value']);
+$tmpl->assign('website', $userData[\OC\Accounts\AccountManager::PROPERTY_WEBSITE]['value']);
+$tmpl->assign('twitter', $userData[\OC\Accounts\AccountManager::PROPERTY_TWITTER]['value']);
+$tmpl->assign('address', $userData[\OC\Accounts\AccountManager::PROPERTY_ADDRESS]['value']);
+
+$tmpl->assign('avatarScope', $userData[\OC\Accounts\AccountManager::PROPERTY_AVATAR]['scope']);
+$tmpl->assign('displayNameScope', $userData[\OC\Accounts\AccountManager::PROPERTY_DISPLAYNAME]['scope']);
+$tmpl->assign('phoneScope', $userData[\OC\Accounts\AccountManager::PROPERTY_PHONE]['scope']);
+$tmpl->assign('emailScope', $userData[\OC\Accounts\AccountManager::PROPERTY_EMAIL]['scope']);
+$tmpl->assign('websiteScope', $userData[\OC\Accounts\AccountManager::PROPERTY_WEBSITE]['scope']);
+$tmpl->assign('twitterScope', $userData[\OC\Accounts\AccountManager::PROPERTY_TWITTER]['scope']);
+$tmpl->assign('addressScope', $userData[\OC\Accounts\AccountManager::PROPERTY_ADDRESS]['scope']);
+
$tmpl->assign('enableAvatars', $config->getSystemValue('enable_avatars', true) === true);
$tmpl->assign('avatarChangeSupported', OC_User::canUserChangeAvatar(OC_User::getUser()));
$tmpl->assign('certs', $certificateManager->listCertificates());
diff --git a/settings/routes.php b/settings/routes.php
index 58a57606312..62cfc398fdc 100644
--- a/settings/routes.php
+++ b/settings/routes.php
@@ -51,7 +51,7 @@ $application->registerRoutes($this, [
['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'],
['name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'],
['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST'],
- ['name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'],
+ ['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'],
['name' => 'Users#stats', 'url' => '/settings/users/stats', 'verb' => 'GET'],
['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'],
['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'],
diff --git a/settings/templates/personal.php b/settings/templates/personal.php
index 8f34d7b87b8..b81b19d9060 100644
--- a/settings/templates/personal.php
+++ b/settings/templates/personal.php
@@ -32,45 +32,128 @@
</div>
</div>
+<div id="personal-settings">
<?php if ($_['enableAvatars']): ?>
-<form id="avatar" class="section" method="post" action="<?php p(\OC::$server->getURLGenerator()->linkToRoute('core.avatar.postAvatar')); ?>">
- <h2><?php p($l->t('Profile picture')); ?></h2>
- <div id="displayavatar">
- <div class="avatardiv"></div>
- <div class="warning hidden"></div>
- <?php if ($_['avatarChangeSupported']): ?>
- <label for="uploadavatar" class="inlineblock button icon-upload" id="uploadavatarbutton" title="<?php p($l->t('Upload new')); ?>"></label>
- <div class="inlineblock button icon-folder" id="selectavatar" title="<?php p($l->t('Select from Files')); ?>"></div>
- <div class="hidden button icon-delete" id="removeavatar" title="<?php p($l->t('Remove image')); ?>"></div>
- <input type="file" name="files[]" id="uploadavatar" class="hiddenuploadfield">
- <p><em><?php p($l->t('png or jpg, max. 20 MB')); ?></em></p>
- <?php else: ?>
- <?php p($l->t('Picture provided by original account')); ?>
- <?php endif; ?>
- </div>
+<div id="personal-settings-avatar-container">
+ <form id="avatarform" class="section" method="post" action="<?php p(\OC::$server->getURLGenerator()->linkToRoute('core.avatar.postAvatar')); ?>">
+ <h2>
+ <label><?php p($l->t('Profile picture')); ?></label>
+ <span class="icon-password"/>
+ </h2>
+ <div id="displayavatar">
+ <div class="avatardiv"></div>
+ <div class="warning hidden"></div>
+ <?php if ($_['avatarChangeSupported']): ?>
+ <label for="uploadavatar" class="inlineblock button icon-upload svg" id="uploadavatarbutton" title="<?php p($l->t('Upload new')); ?>"></label>
+ <div class="inlineblock button icon-folder svg" id="selectavatar" title="<?php p($l->t('Select from Files')); ?>"></div>
+ <div class="hidden button icon-delete svg" id="removeavatar" title="<?php p($l->t('Remove image')); ?>"></div>
+ <input type="file" name="files[]" id="uploadavatar" class="hiddenuploadfield">
+ <p><em><?php p($l->t('png or jpg, max. 20 MB')); ?></em></p>
+ <?php else: ?>
+ <?php p($l->t('Picture provided by original account')); ?>
+ <?php endif; ?>
+ </div>
- <div id="cropper" class="hidden">
- <div class="inner-container">
- <div class="inlineblock button" id="abortcropperbutton"><?php p($l->t('Cancel')); ?></div>
- <div class="inlineblock button primary" id="sendcropperbutton"><?php p($l->t('Choose as profile picture')); ?></div>
+ <div id="cropper" class="hidden">
+ <div class="inner-container">
+ <div class="inlineblock button" id="abortcropperbutton"><?php p($l->t('Cancel')); ?></div>
+ <div class="inlineblock button primary" id="sendcropperbutton"><?php p($l->t('Choose as profile picture')); ?></div>
+ </div>
</div>
- </div>
-</form>
+ <input type="hidden" id="avatarscope" value="<?php p($_['avatarScope']) ?>">
+ </form>
+</div>
<?php endif; ?>
<?php
if($_['displayNameChangeSupported']) {
?>
-<form id="displaynameform" class="section">
- <h2>
- <label for="displayName"><?php echo $l->t('Full name');?></label>
- </h2>
- <input type="text" id="displayName" name="displayName" class="password-confirm-required"
- value="<?php p($_['displayName'])?>"
- autocomplete="on" autocapitalize="off" autocorrect="off" />
- <span class="msg"></span>
- <input type="hidden" id="oldDisplayName" name="oldDisplayName" value="<?php p($_['displayName'])?>" />
-</form>
+<div id="personal-settings-container">
+ <div class="personal-settings-setting-box">
+ <form id="displaynameform" class="section">
+ <h2>
+ <label for="displayname"><?php p($l->t('Full name')); ?></label>
+ <span class="icon-password"/>
+ </h2>
+ <input type="text" id="displayname" name="displayname"
+ value="<?php p($_['displayName']) ?>"
+ autocomplete="on" autocapitalize="off" autocorrect="off" />
+ <span class="icon-checkmark hidden"/>
+ <input type="hidden" id="displaynamescope" value="<?php p($_['displayNameScope']) ?>">
+ </form>
+ </div>
+ <div class="personal-settings-setting-box">
+ <form id="emailform" class="section">
+ <h2>
+ <label for="email"><?php p($l->t('Email')); ?></label>
+ <span class="icon-password"/>
+ </h2>
+ <input type="email" name="email" id="email" value="<?php p($_['email']); ?>"
+ placeholder="<?php p($l->t('Your email address')); ?>"
+ autocomplete="on" autocapitalize="off" autocorrect="off" />
+ <br />
+ <em><?php p($l->t('For password recovery and notifications')); ?></em>
+ <span class="icon-checkmark hidden"/>
+ <input type="hidden" id="emailscope" value="<?php p($_['emailScope']) ?>">
+ </form>
+ </div>
+ <div class="personal-settings-setting-box">
+ <form id="phoneform" class="section">
+ <h2>
+ <label for="phone"><?php p($l->t('Phone number')); ?></label>
+ <span class="icon-password"/>
+ </h2>
+ <input type="tel" id="phone" name="phone"
+ value="<?php p($_['phone']) ?>"
+ placeholder="<?php p($l->t('Your phone number')); ?>"
+ autocomplete="on" autocapitalize="off" autocorrect="off" />
+ <span class="icon-checkmark hidden"/>
+ <input type="hidden" id="phonescope" value="<?php p($_['phoneScope']) ?>">
+ </form>
+ </div>
+ <div class="personal-settings-setting-box">
+ <form id="addressform" class="section">
+ <h2>
+ <label for="address"><?php p($l->t('Address')); ?></label>
+ <span class="icon-password"/>
+ </h2>
+ <input type="text" id="address" name="address"
+ placeholder="<?php p($l->t('Your postal address')); ?>"
+ value="<?php p($_['address']) ?>"
+ autocomplete="on" autocapitalize="off" autocorrect="off" />
+ <span class="icon-checkmark hidden"/>
+ <input type="hidden" id="addressscope" value="<?php p($_['addressScope']) ?>">
+ </form>
+ </div>
+ <div class="personal-settings-setting-box">
+ <form id="websiteform" class="section">
+ <h2>
+ <label for="website"><?php p($l->t('Website')); ?></label>
+ <span class="icon-password"/>
+ </h2>
+ <input type="text" name="website" id="website" value="<?php p($_['website']); ?>"
+ placeholder="<?php p($l->t('Your website')); ?>"
+ autocomplete="on" autocapitalize="off" autocorrect="off" />
+ <span class="icon-checkmark hidden"/>
+ <input type="hidden" id="websitescope" value="<?php p($_['websiteScope']) ?>">
+ </form>
+ </div>
+ <div class="personal-settings-setting-box">
+ <form id="twitterform" class="section">
+ <h2>
+ <label for="twitter"><?php p($l->t('Twitter')); ?></label>
+ <span class="icon-password"/>
+ </h2>
+ <input type="text" name="twitter" id="twitter" value="<?php p($_['twitter']); ?>"
+ placeholder="<?php p($l->t('Your Twitter handle')); ?>"
+ autocomplete="on" autocapitalize="off" autocorrect="off" />
+ <span class="icon-checkmark hidden"/>
+ <input type="hidden" id="twitterscope" value="<?php p($_['twitterScope']) ?>">
+ </form>
+ </div>
+
+ <span class="msg"></span>
+</div>
<?php
} else {
?>
@@ -102,10 +185,37 @@ if($_['displayNameChangeSupported']) {
<div id="lostpassword" class="section">
<h2><?php echo $l->t('Email'); ?></h2>
<span><?php if(isset($_['email'][0])) { p($_['email']); } else { p($l->t('No email address set')); }?></span>
+<div id="personal-settings-container" class="no-edit">
+ <div id="displaynameform" class="section">
+ <h2><?php p($l->t('Full name'));?></h2>
+ <span><?php if(isset($_['displayName'][0])) { p($_['displayName']); } else { p($l->t('No display name set')); } ?></span>
+ </div>
+ <div id="emailform" class="section">
+ <h2><?php p($l->t('Email')); ?></h2>
+ <span><?php if(isset($_['email'][0])) { p($_['email']); } else { p($l->t('No email address set')); }?></span>
+ </div>
+ <div id="phoneform" class="section">
+ <h2><?php p($l->t('Phone')); ?></h2>
+ <span><?php if(isset($_['phone'][0])) { p($_['phone']); } else { p($l->t('No phone number set')); }?></span>
+ </div>
+ <div id="addressform" class="section">
+ <h2><?php p($l->t('Address')); ?></h2>
+ <span><?php if(isset($_['address'][0])) { p($_['address']); } else { p($l->t('No address set')); }?></span>
+ </div>
+ <div id="websiteform" class="section">
+ <h2><?php p($l->t('Website')); ?></h2>
+ <span><?php if(isset($_['website'][0])) { p($_['website']); } else { p($l->t('No website set')); }?></span>
+ </div>
+ <div id="twitterform" class="section">
+ <h2><?php p($l->t('Twitter')); ?></h2>
+ <span><?php if(isset($_['twitter'][0])) { p($_['twitter']); } else { p($l->t('No twitter handle set')); }?></span>
+ </div>
+
</div>
<?php
}
?>
+</div>
<div id="groups" class="section">
<h2><?php p($l->t('Groups')); ?></h2>
@@ -123,17 +233,17 @@ if($_['passwordChangeSupported']) {
<h2 class="inlineblock"><?php p($l->t('Password'));?></h2>
<div id="password-error-msg" class="msg success inlineblock" style="display: none;">Saved</div>
<br>
- <label for="pass1" class="hidden-visually"><?php echo $l->t('Current password');?>: </label>
+ <label for="pass1" class="hidden-visually"><?php p($l->t('Current password')); ?>: </label>
<input type="password" id="pass1" name="oldpassword"
- placeholder="<?php echo $l->t('Current password');?>"
+ placeholder="<?php p($l->t('Current password'));?>"
autocomplete="off" autocapitalize="off" autocorrect="off" />
- <label for="pass2" class="hidden-visually"><?php echo $l->t('New password');?>: </label>
+ <label for="pass2" class="hidden-visually"><?php p($l->t('New password'));?>: </label>
<input type="password" id="pass2" name="newpassword"
- placeholder="<?php echo $l->t('New password');?>"
+ placeholder="<?php p($l->t('New password')); ?>"
data-typetoggle="#personal-show"
autocomplete="off" autocapitalize="off" autocorrect="off" />
<input type="checkbox" id="personal-show" name="show" /><label for="personal-show" class="personal-show-label"></label>
- <input id="passwordbutton" type="submit" value="<?php echo $l->t('Change password');?>" />
+ <input id="passwordbutton" type="submit" value="<?php p($l->t('Change password')); ?>" />
<br/>
</form>
<?php
diff --git a/tests/Core/Controller/OCSControllerTest.php b/tests/Core/Controller/OCSControllerTest.php
index 38356483c95..6c47521786f 100644
--- a/tests/Core/Controller/OCSControllerTest.php
+++ b/tests/Core/Controller/OCSControllerTest.php
@@ -24,6 +24,8 @@ namespace OC\Core\Controller;
use OC\CapabilitiesManager;
use OC\Security\Bruteforce\Throttler;
+use OC\Security\IdentityProof\Key;
+use OC\Security\IdentityProof\Manager;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
use OCP\IUser;
@@ -32,22 +34,18 @@ use OCP\IUserSession;
use Test\TestCase;
class OCSControllerTest extends TestCase {
-
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
private $request;
-
/** @var CapabilitiesManager|\PHPUnit_Framework_MockObject_MockObject */
private $capabilitiesManager;
-
/** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */
private $userSession;
-
/** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
private $userManager;
-
/** @var Throttler|\PHPUnit_Framework_MockObject_MockObject */
private $throttler;
-
+ /** @var Manager|\PHPUnit_Framework_MockObject_MockObject */
+ private $keyManager;
/** @var OCSController */
private $controller;
@@ -59,6 +57,7 @@ class OCSControllerTest extends TestCase {
$this->userSession = $this->createMock(IUserSession::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->throttler = $this->createMock(Throttler::class);
+ $this->keyManager = $this->createMock(Manager::class);
$this->controller = new OCSController(
'core',
@@ -66,7 +65,8 @@ class OCSControllerTest extends TestCase {
$this->capabilitiesManager,
$this->userSession,
$this->userManager,
- $this->throttler
+ $this->throttler,
+ $this->keyManager
);
}
@@ -206,4 +206,39 @@ class OCSControllerTest extends TestCase {
$this->assertEquals($expected, $this->controller->personCheck('', ''));
}
+
+ public function testGetIdentityProofWithNotExistingUser() {
+ $this->userManager
+ ->expects($this->once())
+ ->method('get')
+ ->with('NotExistingUser')
+ ->willReturn(null);
+
+ $expected = new DataResponse('User not found', 404);
+ $this->assertEquals($expected, $this->controller->getIdentityProof('NotExistingUser'));
+ }
+
+ public function testGetIdentityProof() {
+ $user = $this->createMock(IUser::class);
+ $key = $this->createMock(Key::class);
+ $this->userManager
+ ->expects($this->once())
+ ->method('get')
+ ->with('ExistingUser')
+ ->willReturn($user);
+ $this->keyManager
+ ->expects($this->once())
+ ->method('getKey')
+ ->with($user)
+ ->willReturn($key);
+ $key
+ ->expects($this->once())
+ ->method('getPublic')
+ ->willReturn('Existing Users public key');
+
+ $expected = new DataResponse([
+ 'public' => 'Existing Users public key',
+ ]);
+ $this->assertEquals($expected, $this->controller->getIdentityProof('ExistingUser'));
+ }
}
diff --git a/tests/Settings/Controller/UsersControllerTest.php b/tests/Settings/Controller/UsersControllerTest.php
index 03c3a2e2ab4..ec92479b40e 100644
--- a/tests/Settings/Controller/UsersControllerTest.php
+++ b/tests/Settings/Controller/UsersControllerTest.php
@@ -10,6 +10,7 @@
namespace Tests\Settings\Controller;
+use OC\Accounts\AccountManager;
use OC\Group\Manager;
use OC\Settings\Controller\UsersController;
use OCP\App\IAppManager;
@@ -57,6 +58,8 @@ class UsersControllerTest extends \Test\TestCase {
private $avatarManager;
/** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */
private $l;
+ /** @var AccountManager | \PHPUnit_Framework_MockObject_MockObject */
+ private $accountManager;
protected function setUp() {
parent::setUp();
@@ -71,6 +74,7 @@ class UsersControllerTest extends \Test\TestCase {
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->appManager = $this->createMock(IAppManager::class);
$this->avatarManager = $this->createMock(IAvatarManager::class);
+ $this->accountManager = $this->createMock(AccountManager::class);
$this->l = $this->createMock(IL10N::class);
$this->l->method('t')
->will($this->returnCallback(function ($text, $parameters = []) {
@@ -117,7 +121,8 @@ class UsersControllerTest extends \Test\TestCase {
'no-reply@owncloud.com',
$this->urlGenerator,
$this->appManager,
- $this->avatarManager
+ $this->avatarManager,
+ $this->accountManager
);
}
@@ -1760,74 +1765,6 @@ class UsersControllerTest extends \Test\TestCase {
$this->assertEquals($expectedResult, $result);
}
- /**
- * @return array
- */
- public function setEmailAddressData() {
- return [
- /* mailAddress, isValid, expectsUpdate, canChangeDisplayName, responseCode */
- [ '', true, true, true, Http::STATUS_OK ],
- [ 'foo@local', true, true, true, Http::STATUS_OK],
- [ 'foo@bar@local', false, false, true, Http::STATUS_UNPROCESSABLE_ENTITY],
- [ 'foo@local', true, false, false, Http::STATUS_FORBIDDEN],
- ];
- }
-
- /**
- * @dataProvider setEmailAddressData
- *
- * @param string $mailAddress
- * @param bool $isValid
- * @param bool $expectsUpdate
- * @param bool $expectsDelete
- */
- public function testSetEmailAddress($mailAddress, $isValid, $expectsUpdate, $canChangeDisplayName, $responseCode) {
- $controller = $this->getController(true);
-
- $user = $this->getMockBuilder('\OC\User\User')
- ->disableOriginalConstructor()->getMock();
- $user
- ->expects($this->any())
- ->method('getUID')
- ->will($this->returnValue('foo'));
- $user
- ->expects($this->any())
- ->method('canChangeDisplayName')
- ->will($this->returnValue($canChangeDisplayName));
- $user
- ->expects($expectsUpdate ? $this->once() : $this->never())
- ->method('setEMailAddress')
- ->with(
- $this->equalTo($mailAddress)
- );
-
- $this->userSession
- ->expects($this->atLeastOnce())
- ->method('getUser')
- ->will($this->returnValue($user));
- $this->mailer
- ->expects($this->any())
- ->method('validateMailAddress')
- ->with($mailAddress)
- ->willReturn($isValid);
-
- if ($isValid) {
- $user->expects($this->atLeastOnce())
- ->method('canChangeDisplayName')
- ->willReturn(true);
-
- $this->userManager
- ->expects($this->atLeastOnce())
- ->method('get')
- ->with('foo')
- ->will($this->returnValue($user));
- }
-
- $response = $controller->setMailAddress($user->getUID(), $mailAddress);
-
- $this->assertSame($responseCode, $response->getStatus());
- }
-
public function testStatsAdmin() {
$controller = $this->getController(true);
@@ -1976,6 +1913,7 @@ class UsersControllerTest extends \Test\TestCase {
->method('get')
->with($editUser->getUID())
->willReturn($editUser);
+ $this->accountManager->expects($this->any())->method('getUser')->willReturn([]);
$subadmin = $this->getMockBuilder('\OC\SubAdmin')
->disableOriginalConstructor()
@@ -1994,10 +1932,6 @@ class UsersControllerTest extends \Test\TestCase {
->willReturn($isAdmin);
if ($valid === true) {
- $editUser->expects($this->once())
- ->method('setDisplayName')
- ->with('newDisplayName')
- ->willReturn(true);
$expectedResponse = new DataResponse(
[
'status' => 'success',
@@ -2009,7 +1943,6 @@ class UsersControllerTest extends \Test\TestCase {
]
);
} else {
- $editUser->expects($this->never())->method('setDisplayName');
$expectedResponse = new DataResponse(
[
'status' => 'error',
@@ -2040,6 +1973,7 @@ class UsersControllerTest extends \Test\TestCase {
->expects($this->once())
->method('getUser')
->willReturn($user);
+
$this->userManager
->expects($this->once())
->method('get')
diff --git a/tests/lib/Accounts/AccountsManagerTest.php b/tests/lib/Accounts/AccountsManagerTest.php
new file mode 100644
index 00000000000..de88fbcab97
--- /dev/null
+++ b/tests/lib/Accounts/AccountsManagerTest.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * @author Björn Schießle <schiessle@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+namespace Test\Accounts;
+
+
+use OC\Accounts\AccountManager;
+use OC\Mail\Mailer;
+use OCP\IUser;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Test\TestCase;
+
+/**
+ * Class AccountsManagerTest
+ *
+ * @group DB
+ * @package Test\Accounts
+ */
+class AccountsManagerTest extends TestCase {
+
+ /** @var \OCP\IDBConnection */
+ private $connection;
+
+ /** @var EventDispatcherInterface | \PHPUnit_Framework_MockObject_MockObject */
+ private $eventDispatcher;
+
+ /** @var string accounts table name */
+ private $table = 'accounts';
+
+ public function setUp() {
+ parent::setUp();
+ $this->eventDispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')
+ ->disableOriginalConstructor()->getMock();
+ $this->connection = \OC::$server->getDatabaseConnection();
+ }
+
+ public function tearDown() {
+ parent::tearDown();
+ $query = $this->connection->getQueryBuilder();
+ $query->delete($this->table)->execute();
+ }
+
+ /**
+ * get a instance of the accountManager
+ *
+ * @param array $mockedMethods list of methods which should be mocked
+ * @return \PHPUnit_Framework_MockObject_MockObject | AccountManager
+ */
+ public function getInstance($mockedMethods = null) {
+ return $this->getMockBuilder('OC\Accounts\AccountManager')
+ ->setConstructorArgs([$this->connection, $this->eventDispatcher])
+ ->setMethods($mockedMethods)
+ ->getMock();
+
+ }
+
+ /**
+ * @dataProvider dataTrueFalse
+ *
+ * @param bool $userAlreadyExists
+ */
+ public function testUpdateUser($userAlreadyExists) {
+ $accountManager = $this->getInstance(['getUser', 'insertNewUser', 'updateExistingUser']);
+ $user = $this->getMockBuilder('OCP\IUser')->getMock();
+
+ $accountManager->expects($this->once())->method('getUser')->with($user)->willReturn($userAlreadyExists);
+
+ if ($userAlreadyExists) {
+ $accountManager->expects($this->once())->method('updateExistingUser')
+ ->with($user, 'data');
+ $accountManager->expects($this->never())->method('insertNewUser');
+ } else {
+ $accountManager->expects($this->once())->method('insertNewUser')
+ ->with($user, 'data');
+ $accountManager->expects($this->never())->method('updateExistingUser');
+ }
+
+ $this->eventDispatcher->expects($this->once())->method('dispatch')
+ ->willReturnCallback(function($eventName, $event) use ($user) {
+ $this->assertSame('OC\AccountManager::userUpdated', $eventName);
+ $this->assertInstanceOf('Symfony\Component\EventDispatcher\GenericEvent', $event);
+ }
+ );
+
+ $accountManager->updateUser($user, 'data');
+ }
+
+ public function dataTrueFalse() {
+ return [
+ [true],
+ [false]
+ ];
+ }
+
+
+ /**
+ * @dataProvider dataTestGetUser
+ *
+ * @param string $setUser
+ * @param array $setData
+ * @param IUser $askUser
+ * @param array $expectedData
+ * @param book $userAlreadyExists
+ */
+ public function testGetUser($setUser, $setData, $askUser, $expectedData, $userAlreadyExists) {
+ $accountManager = $this->getInstance(['buildDefaultUserRecord', 'insertNewUser']);
+ if (!$userAlreadyExists) {
+ $accountManager->expects($this->once())->method('buildDefaultUserRecord')
+ ->with($askUser)->willReturn($expectedData);
+ $accountManager->expects($this->once())->method('insertNewUser')
+ ->with($askUser, $expectedData);
+ }
+ $this->addDummyValuesToTable($setUser, $setData);
+ $this->assertEquals($expectedData,
+ $accountManager->getUser($askUser)
+ );
+ }
+
+ public function dataTestGetUser() {
+ $user1 = $this->getMockBuilder('OCP\IUser')->getMock();
+ $user1->expects($this->any())->method('getUID')->willReturn('user1');
+ $user2 = $this->getMockBuilder('OCP\IUser')->getMock();
+ $user2->expects($this->any())->method('getUID')->willReturn('user2');
+ return [
+ ['user1', ['key' => 'value'], $user1, ['key' => 'value'], true],
+ ['user1', ['key' => 'value'], $user2, [], false],
+ ];
+ }
+
+ public function testUpdateExistingUser() {
+ $user = $this->getMockBuilder('OCP\IUser')->getMock();
+ $user->expects($this->once())->method('getUID')->willReturn('uid');
+ $oldData = ['key' => 'value'];
+ $newData = ['newKey' => 'newValue'];
+
+ $accountManager = $this->getInstance();
+ $this->addDummyValuesToTable('uid', $oldData);
+ $this->invokePrivate($accountManager, 'updateExistingUser', [$user, $newData]);
+ $newDataFromTable = $this->getDataFromTable('uid');
+ $this->assertEquals($newData, $newDataFromTable);
+ }
+
+ public function testInsertNewUser() {
+ $user = $this->getMockBuilder('OCP\IUser')->getMock();
+ $uid = 'uid';
+ $data = ['key' => 'value'];
+
+ $accountManager = $this->getInstance();
+ $user->expects($this->once())->method('getUID')->willReturn($uid);
+ $this->assertNull($this->getDataFromTable($uid));
+ $this->invokePrivate($accountManager, 'insertNewUser', [$user, $data]);
+
+ $dataFromDb = $this->getDataFromTable($uid);
+ $this->assertEquals($data, $dataFromDb);
+ }
+
+ private function addDummyValuesToTable($uid, $data) {
+
+ $query = $this->connection->getQueryBuilder();
+ $query->insert($this->table)
+ ->values(
+ [
+ 'uid' => $query->createNamedParameter($uid),
+ 'data' => $query->createNamedParameter(json_encode($data)),
+ ]
+ )
+ ->execute();
+ }
+
+ private function getDataFromTable($uid) {
+ $query = $this->connection->getQueryBuilder();
+ $query->select('data')->from($this->table)
+ ->where($query->expr()->eq('uid', $query->createParameter('uid')))
+ ->setParameter('uid', $uid);
+ $query->execute();
+ $result = $query->execute()->fetchAll();
+
+ if (!empty($result)) {
+ return json_decode($result[0]['data'], true);
+ }
+ }
+
+}
diff --git a/tests/lib/App/ManagerTest.php b/tests/lib/App/ManagerTest.php
index 3dbcb8a5609..e38f72b3d92 100644
--- a/tests/lib/App/ManagerTest.php
+++ b/tests/lib/App/ManagerTest.php
@@ -320,6 +320,7 @@ class ManagerTest extends TestCase {
'dav',
'federatedfilesharing',
'files',
+ 'lookup_server_connector',
'provisioning_api',
'test1',
'test3',
@@ -344,6 +345,7 @@ class ManagerTest extends TestCase {
'dav',
'federatedfilesharing',
'files',
+ 'lookup_server_connector',
'provisioning_api',
'test1',
'test3',
@@ -364,6 +366,7 @@ class ManagerTest extends TestCase {
'files' => ['id' => 'files'],
'federatedfilesharing' => ['id' => 'federatedfilesharing'],
'provisioning_api' => ['id' => 'provisioning_api'],
+ 'lookup_server_connector' => ['id' => 'lookup_server_connector'],
'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '9.0.0'],
'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'],
'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'],
@@ -408,6 +411,7 @@ class ManagerTest extends TestCase {
'files' => ['id' => 'files'],
'federatedfilesharing' => ['id' => 'federatedfilesharing'],
'provisioning_api' => ['id' => 'provisioning_api'],
+ 'lookup_server_connector' => ['id' => 'lookup_server_connector'],
'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '8.0.0'],
'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'],
'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'],
diff --git a/tests/lib/AppTest.php b/tests/lib/AppTest.php
index 971d86cf6a4..575e32dd60c 100644
--- a/tests/lib/AppTest.php
+++ b/tests/lib/AppTest.php
@@ -351,6 +351,7 @@ class AppTest extends \Test\TestCase {
'appforgroup12',
'dav',
'federatedfilesharing',
+ 'lookup_server_connector',
'provisioning_api',
'twofactor_backupcodes',
'workflowengine',
@@ -368,6 +369,7 @@ class AppTest extends \Test\TestCase {
'appforgroup2',
'dav',
'federatedfilesharing',
+ 'lookup_server_connector',
'provisioning_api',
'twofactor_backupcodes',
'workflowengine',
@@ -386,6 +388,7 @@ class AppTest extends \Test\TestCase {
'appforgroup2',
'dav',
'federatedfilesharing',
+ 'lookup_server_connector',
'provisioning_api',
'twofactor_backupcodes',
'workflowengine',
@@ -404,6 +407,7 @@ class AppTest extends \Test\TestCase {
'appforgroup2',
'dav',
'federatedfilesharing',
+ 'lookup_server_connector',
'provisioning_api',
'twofactor_backupcodes',
'workflowengine',
@@ -422,6 +426,7 @@ class AppTest extends \Test\TestCase {
'appforgroup2',
'dav',
'federatedfilesharing',
+ 'lookup_server_connector',
'provisioning_api',
'twofactor_backupcodes',
'workflowengine',
@@ -502,11 +507,11 @@ class AppTest extends \Test\TestCase {
);
$apps = \OC_App::getEnabledApps();
- $this->assertEquals(array('files', 'app3', 'dav', 'federatedfilesharing', 'provisioning_api', 'twofactor_backupcodes', 'workflowengine'), $apps);
+ $this->assertEquals(array('files', 'app3', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'provisioning_api', 'twofactor_backupcodes', 'workflowengine'), $apps);
// mock should not be called again here
$apps = \OC_App::getEnabledApps();
- $this->assertEquals(array('files', 'app3', 'dav', 'federatedfilesharing', 'provisioning_api', 'twofactor_backupcodes', 'workflowengine'), $apps);
+ $this->assertEquals(array('files', 'app3', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'provisioning_api', 'twofactor_backupcodes', 'workflowengine'), $apps);
$this->restoreAppConfig();
\OC_User::setUserId(null);
diff --git a/tests/lib/Security/IdentityProof/ManagerTest.php b/tests/lib/Security/IdentityProof/ManagerTest.php
new file mode 100644
index 00000000000..d93f675825b
--- /dev/null
+++ b/tests/lib/Security/IdentityProof/ManagerTest.php
@@ -0,0 +1,166 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
+ *
+ * @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 Test\Security;
+
+use OC\Security\IdentityProof\Key;
+use OC\Security\IdentityProof\Manager;
+use OCP\Files\IAppData;
+use OCP\Files\SimpleFS\ISimpleFile;
+use OCP\Files\SimpleFS\ISimpleFolder;
+use OCP\IUser;
+use OCP\Security\ICrypto;
+use Test\TestCase;
+
+class ManagerTest extends TestCase {
+ /** @var IAppData|\PHPUnit_Framework_MockObject_MockObject */
+ private $appData;
+ /** @var ICrypto|\PHPUnit_Framework_MockObject_MockObject */
+ private $crypto;
+ /** @var Manager|\PHPUnit_Framework_MockObject_MockObject */
+ private $manager;
+
+ public function setUp() {
+ parent::setUp();
+ $this->appData = $this->createMock(IAppData::class);
+ $this->crypto = $this->createMock(ICrypto::class);
+ $this->manager = $this->getMockBuilder(Manager::class)
+ ->setConstructorArgs([
+ $this->appData,
+ $this->crypto
+ ])
+ ->setMethods(['generateKeyPair'])
+ ->getMock();
+ }
+
+ public function testGetKeyWithExistingKey() {
+ $user = $this->createMock(IUser::class);
+ $user
+ ->expects($this->once())
+ ->method('getUID')
+ ->willReturn('MyUid');
+ $folder = $this->createMock(ISimpleFolder::class);
+ $privateFile = $this->createMock(ISimpleFile::class);
+ $privateFile
+ ->expects($this->once())
+ ->method('getContent')
+ ->willReturn('EncryptedPrivateKey');
+ $publicFile = $this->createMock(ISimpleFile::class);
+ $publicFile
+ ->expects($this->once())
+ ->method('getContent')
+ ->willReturn('MyPublicKey');
+ $this->crypto
+ ->expects($this->once())
+ ->method('decrypt')
+ ->with('EncryptedPrivateKey')
+ ->willReturn('MyPrivateKey');
+ $folder
+ ->expects($this->at(0))
+ ->method('getFile')
+ ->with('private')
+ ->willReturn($privateFile);
+ $folder
+ ->expects($this->at(1))
+ ->method('getFile')
+ ->with('public')
+ ->willReturn($publicFile);
+ $this->appData
+ ->expects($this->once())
+ ->method('getFolder')
+ ->with('MyUid')
+ ->willReturn($folder);
+
+ $expected = new Key('MyPublicKey', 'MyPrivateKey');
+ $this->assertEquals($expected, $this->manager->getKey($user));
+ }
+
+ public function testGetKeyWithNotExistingKey() {
+ $user = $this->createMock(IUser::class);
+ $user
+ ->expects($this->exactly(3))
+ ->method('getUID')
+ ->willReturn('MyUid');
+ $this->appData
+ ->expects($this->at(0))
+ ->method('getFolder')
+ ->with('MyUid')
+ ->willThrowException(new \Exception());
+ $this->manager
+ ->expects($this->once())
+ ->method('generateKeyPair')
+ ->willReturn(['MyNewPublicKey', 'MyNewPrivateKey']);
+ $this->appData
+ ->expects($this->at(1))
+ ->method('newFolder')
+ ->with('MyUid');
+ $folder = $this->createMock(ISimpleFolder::class);
+ $this->crypto
+ ->expects($this->once())
+ ->method('encrypt')
+ ->with('MyNewPrivateKey')
+ ->willReturn('MyNewEncryptedPrivateKey');
+ $privateFile = $this->createMock(ISimpleFile::class);
+ $privateFile
+ ->expects($this->once())
+ ->method('putContent')
+ ->with('MyNewEncryptedPrivateKey');
+ $publicFile = $this->createMock(ISimpleFile::class);
+ $publicFile
+ ->expects($this->once())
+ ->method('putContent')
+ ->with('MyNewPublicKey');
+ $folder
+ ->expects($this->at(0))
+ ->method('newFile')
+ ->with('private')
+ ->willReturn($privateFile);
+ $folder
+ ->expects($this->at(1))
+ ->method('newFile')
+ ->with('public')
+ ->willReturn($publicFile);
+ $this->appData
+ ->expects($this->at(2))
+ ->method('getFolder')
+ ->with('MyUid')
+ ->willReturn($folder);
+
+
+ $expected = new Key('MyNewPublicKey', 'MyNewPrivateKey');
+ $this->assertEquals($expected, $this->manager->getKey($user));
+ }
+
+ public function testGenerateKeyPair() {
+ $manager = new Manager(
+ $this->appData,
+ $this->crypto
+ );
+ $data = 'MyTestData';
+
+ list($resultPublicKey, $resultPrivateKey) = $this->invokePrivate($manager, 'generateKeyPair');
+ openssl_sign($data, $signature, $resultPrivateKey);
+ $details = openssl_pkey_get_details(openssl_pkey_get_public($resultPublicKey));
+
+ $this->assertSame(1, openssl_verify($data, $signature, $resultPublicKey));
+ $this->assertSame(2048, $details['bits']);
+ }
+}
diff --git a/version.php b/version.php
index d556386a848..0eb9f3de72a 100644
--- a/version.php
+++ b/version.php
@@ -25,7 +25,8 @@
// We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
// when updating major/minor version number.
-$OC_Version = array(11, 0, 0, 1);
+
+$OC_Version = array(11, 0, 0, 2);
// The human readable string
$OC_VersionString = '11.0 alpha';