Merge pull request #1946 from nextcloud/federated-sharing-persona-settings
Add more personal information fields to the settings page for enhanced federated sharingpull/2215/head
commit
94004cf46b
@ -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);
|
||||
});
|
||||
@ -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>
|
||||
@ -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,
|
||||
]
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
})();
|
||||
@ -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);
|
||||
@ -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;
|
||||
})();
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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']);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue