Merge pull request #1946 from nextcloud/federated-sharing-persona-settings

Add more personal information fields to the settings page for enhanced federated sharing
This commit is contained in:
Lukas Reschke 2016-11-21 13:50:03 +01:00 committed by GitHub
commit 94004cf46b
51 changed files with 2527 additions and 427 deletions

1
.gitignore vendored
View file

@ -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

View file

@ -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);

View file

@ -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;
/**
* Converter constructor.
*
* @param AccountManager $accountManager
*/
public function __construct(AccountManager $accountManager) {
$this->accountManager = $accountManager;
}
/**
* @param IUser $user
* @return VCard
* @return VCard|null
*/
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);
$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();
$vCard->add(new Text($vCard, 'UID', $uid));
return $vCard;
}
$publish = false;
/**
* @param VCard $vCard
* @param IUser $user
* @return bool
*/
public function updateCard(VCard $vCard, IUser $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;
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;
}
}
}
if($this->propertyNeedsUpdate($vCard, 'PHOTO', $image)) {
unset($vCard->PHOTO);
$vCard->add('PHOTO', $image->data(), ['ENCODING' => 'b', 'TYPE' => $image->mimeType()]);
$updated = true;
if ($publish && !empty($cloudId)) {
$vCard->add(new Text($vCard, 'CLOUD', $cloudId));
$vCard->validate();
return $vCard;
}
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;
}
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;
}
$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;
}
/**

View file

@ -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());
}
}

View file

@ -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() {

View file

@ -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 {
/** @var AccountManager | PHPUnit_Framework_MockObject_MockObject */
private $accountManager;
/** @var EventDispatcher | PHPUnit_Framework_MockObject_MockObject */
private $eventDispatcher;
/** @var IDBConnection | PHPUnit_Framework_MockObject_MockObject */
private $databaseConnection;
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();
}
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 providesNewUsers
*/
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);
$cardData = $vCard->serialize();
if ($expectedVCard !== null) {
$this->assertInstanceOf('Sabre\VObject\Component\VCard', $vCard);
$cardData = $vCard->jsonSerialize();
$this->compareData($expectedVCard, $cardData);
$this->assertEquals($expectedVCard, $cardData);
} else {
$this->assertSame($expectedVCard, $vCard);
}
}
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 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"],
];
}
/**
* @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);
}
/**
* @dataProvider providesUsersForUpdateOfRemovedElement
*/
public function testUpdateOfRemovedElement($expectedVCard, $displayName = null, $eMailAddress = null, $cloudId = null) {
$user = $this->getUserMock($displayName, $eMailAddress, $cloudId);
$converter = new Converter();
$vCard = $converter->createCardFromUser($user);
$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);
$updated = $converter->updateCard($vCard, $user1);
$this->assertTrue($updated);
$cardData = $vCard->serialize();
$this->assertEquals($expectedVCard, $cardData);
}
public function providesUsersForUpdateOfRemovedElement() {
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);

View file

@ -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');
$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);
$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]);

View file

@ -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']);
}

View file

@ -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;
}
}

View file

@ -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, '');

View file

@ -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>

View file

@ -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());

View file

@ -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,

View file

@ -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
*

View file

@ -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();

View file

@ -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);
});

View file

@ -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>

View file

@ -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,
]
);
}
}
}

View file

@ -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,
]
);
}
}
}

View file

@ -290,6 +290,7 @@ Feature: provisioning
| files_sharing |
| files_trashbin |
| files_versions |
| lookup_server_connector |
| provisioning_api |
| sharebymail |
| systemtags |

View file

@ -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()
);
});
}
}

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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 */

View file

@ -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')

View file

@ -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': []
}
}
});

View file

@ -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'],
],
]);

View file

@ -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"

View file

@ -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>

View file

@ -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,
],
];
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -53,7 +53,7 @@ class Updater extends BasicEmitter {
/** @var ILogger $log */
private $log;
/** @var IConfig */
private $config;

View file

@ -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

View file

@ -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);
public function setUserSettings($avatarScope,
$displayname,
$displaynameScope,
$phone,
$phoneScope,
$email,
$emailScope,
$website,
$websiteScope,
$address,
$addressScope,
$twitter,
$twitterScope
) {
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)) {
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(),
],
]);

View file

@ -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;

View file

@ -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;
})();

View file

@ -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);

View file

@ -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;

View file

@ -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;
})();

View file

@ -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());

View file

@ -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'],

View file

@ -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="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="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>
</form>
<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>
<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

View file

@ -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'));
}
}

View file

@ -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')

View file

@ -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);
}
}
}

View file

@ -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'],

View file

@ -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);

View file

@ -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']);
}
}

View file

@ -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';