You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							359 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							359 lines
						
					
					
						
							11 KiB
						
					
					
				
								<?php
							 | 
						|
								/**
							 | 
						|
								 * @copyright Copyright (c) 2016, ownCloud, Inc.
							 | 
						|
								 * @copyright Copyright (c) 2016, Björn Schießle
							 | 
						|
								 *
							 | 
						|
								 * @author Bjoern Schiessle <bjoern@schiessle.org>
							 | 
						|
								 * @author Björn Schießle <bjoern@schiessle.org>
							 | 
						|
								 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
							 | 
						|
								 * @author Daniel Kesselberg <mail@danielkesselberg.de>
							 | 
						|
								 * @author Joas Schilling <coding@schilljs.com>
							 | 
						|
								 * @author Julius Härtl <jus@bitgrid.net>
							 | 
						|
								 * @author Morris Jobke <hey@morrisjobke.de>
							 | 
						|
								 * @author Roeland Jago Douma <roeland@famdouma.nl>
							 | 
						|
								 *
							 | 
						|
								 * @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 OCA\Settings\BackgroundJobs\VerifyUserData;
							 | 
						|
								use OCP\Accounts\IAccount;
							 | 
						|
								use OCP\Accounts\IAccountManager;
							 | 
						|
								use OCP\BackgroundJob\IJobList;
							 | 
						|
								use OCP\IDBConnection;
							 | 
						|
								use OCP\ILogger;
							 | 
						|
								use OCP\IUser;
							 | 
						|
								use Symfony\Component\EventDispatcher\EventDispatcherInterface;
							 | 
						|
								use Symfony\Component\EventDispatcher\GenericEvent;
							 | 
						|
								use function json_decode;
							 | 
						|
								use function json_last_error;
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Class AccountManager
							 | 
						|
								 *
							 | 
						|
								 * Manage system accounts table
							 | 
						|
								 *
							 | 
						|
								 * @group DB
							 | 
						|
								 * @package OC\Accounts
							 | 
						|
								 */
							 | 
						|
								class AccountManager implements IAccountManager {
							 | 
						|
								
							 | 
						|
									/** @var  IDBConnection database connection */
							 | 
						|
									private $connection;
							 | 
						|
								
							 | 
						|
									/** @var string table name */
							 | 
						|
									private $table = 'accounts';
							 | 
						|
								
							 | 
						|
									/** @var EventDispatcherInterface */
							 | 
						|
									private $eventDispatcher;
							 | 
						|
								
							 | 
						|
									/** @var IJobList */
							 | 
						|
									private $jobList;
							 | 
						|
								
							 | 
						|
									/** @var ILogger */
							 | 
						|
									private $logger;
							 | 
						|
								
							 | 
						|
									/**
							 | 
						|
									 * AccountManager constructor.
							 | 
						|
									 *
							 | 
						|
									 * @param IDBConnection $connection
							 | 
						|
									 * @param EventDispatcherInterface $eventDispatcher
							 | 
						|
									 * @param IJobList $jobList
							 | 
						|
									 */
							 | 
						|
									public function __construct(IDBConnection $connection,
							 | 
						|
																EventDispatcherInterface $eventDispatcher,
							 | 
						|
																IJobList $jobList,
							 | 
						|
																ILogger $logger) {
							 | 
						|
										$this->connection = $connection;
							 | 
						|
										$this->eventDispatcher = $eventDispatcher;
							 | 
						|
										$this->jobList = $jobList;
							 | 
						|
										$this->logger = $logger;
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									/**
							 | 
						|
									 * update user record
							 | 
						|
									 *
							 | 
						|
									 * @param IUser $user
							 | 
						|
									 * @param $data
							 | 
						|
									 */
							 | 
						|
									public function updateUser(IUser $user, $data) {
							 | 
						|
										$userData = $this->getUser($user);
							 | 
						|
										$updated = true;
							 | 
						|
										if (empty($userData)) {
							 | 
						|
											$this->insertNewUser($user, $data);
							 | 
						|
										} elseif ($userData !== $data) {
							 | 
						|
											$data = $this->checkEmailVerification($userData, $data, $user);
							 | 
						|
											$data = $this->updateVerifyStatus($userData, $data);
							 | 
						|
											$this->updateExistingUser($user, $data);
							 | 
						|
										} else {
							 | 
						|
											// nothing needs to be done if new and old data set are the same
							 | 
						|
											$updated = false;
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if ($updated) {
							 | 
						|
											$this->eventDispatcher->dispatch(
							 | 
						|
												'OC\AccountManager::userUpdated',
							 | 
						|
												new GenericEvent($user, $data)
							 | 
						|
											);
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									/**
							 | 
						|
									 * delete user from accounts table
							 | 
						|
									 *
							 | 
						|
									 * @param IUser $user
							 | 
						|
									 */
							 | 
						|
									public function deleteUser(IUser $user) {
							 | 
						|
										$uid = $user->getUID();
							 | 
						|
										$query = $this->connection->getQueryBuilder();
							 | 
						|
										$query->delete($this->table)
							 | 
						|
											->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
							 | 
						|
											->execute();
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									/**
							 | 
						|
									 * 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;
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										$userDataArray = json_decode($result[0]['data'], true);
							 | 
						|
										$jsonError = json_last_error();
							 | 
						|
										if ($userDataArray === null || $jsonError !== JSON_ERROR_NONE) {
							 | 
						|
											$this->logger->critical("User data of $uid contained invalid JSON (error $jsonError), hence falling back to a default user record");
							 | 
						|
											return $this->buildDefaultUserRecord($user);
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										$userDataArray = $this->addMissingDefaultValues($userDataArray);
							 | 
						|
								
							 | 
						|
										return $userDataArray;
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									/**
							 | 
						|
									 * check if we need to ask the server for email verification, if yes we create a cronjob
							 | 
						|
									 *
							 | 
						|
									 * @param $oldData
							 | 
						|
									 * @param $newData
							 | 
						|
									 * @param IUser $user
							 | 
						|
									 * @return array
							 | 
						|
									 */
							 | 
						|
									protected function checkEmailVerification($oldData, $newData, IUser $user) {
							 | 
						|
										if ($oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']) {
							 | 
						|
											$this->jobList->add(VerifyUserData::class,
							 | 
						|
												[
							 | 
						|
													'verificationCode' => '',
							 | 
						|
													'data' => $newData[self::PROPERTY_EMAIL]['value'],
							 | 
						|
													'type' => self::PROPERTY_EMAIL,
							 | 
						|
													'uid' => $user->getUID(),
							 | 
						|
													'try' => 0,
							 | 
						|
													'lastRun' => time()
							 | 
						|
												]
							 | 
						|
											);
							 | 
						|
											$newData[AccountManager::PROPERTY_EMAIL]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										return $newData;
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									/**
							 | 
						|
									 * make sure that all expected data are set
							 | 
						|
									 *
							 | 
						|
									 * @param array $userData
							 | 
						|
									 * @return array
							 | 
						|
									 */
							 | 
						|
									protected function addMissingDefaultValues(array $userData) {
							 | 
						|
								
							 | 
						|
										foreach ($userData as $key => $value) {
							 | 
						|
											if (!isset($userData[$key]['verified'])) {
							 | 
						|
												$userData[$key]['verified'] = self::NOT_VERIFIED;
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										return $userData;
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									/**
							 | 
						|
									 * reset verification status if personal data changed
							 | 
						|
									 *
							 | 
						|
									 * @param array $oldData
							 | 
						|
									 * @param array $newData
							 | 
						|
									 * @return array
							 | 
						|
									 */
							 | 
						|
									protected function updateVerifyStatus($oldData, $newData) {
							 | 
						|
								
							 | 
						|
										// which account was already verified successfully?
							 | 
						|
										$twitterVerified = isset($oldData[self::PROPERTY_TWITTER]['verified']) && $oldData[self::PROPERTY_TWITTER]['verified'] === self::VERIFIED;
							 | 
						|
										$websiteVerified = isset($oldData[self::PROPERTY_WEBSITE]['verified']) && $oldData[self::PROPERTY_WEBSITE]['verified'] === self::VERIFIED;
							 | 
						|
										$emailVerified = isset($oldData[self::PROPERTY_EMAIL]['verified']) && $oldData[self::PROPERTY_EMAIL]['verified'] === self::VERIFIED;
							 | 
						|
								
							 | 
						|
										// keep old verification status if we don't have a new one
							 | 
						|
										if(!isset($newData[self::PROPERTY_TWITTER]['verified'])) {
							 | 
						|
											// keep old verification status if value didn't changed and an old value exists
							 | 
						|
											$keepOldStatus = $newData[self::PROPERTY_TWITTER]['value'] === $oldData[self::PROPERTY_TWITTER]['value'] && isset($oldData[self::PROPERTY_TWITTER]['verified']);
							 | 
						|
											$newData[self::PROPERTY_TWITTER]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_TWITTER]['verified'] : self::NOT_VERIFIED;
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if(!isset($newData[self::PROPERTY_WEBSITE]['verified'])) {
							 | 
						|
											// keep old verification status if value didn't changed and an old value exists
							 | 
						|
											$keepOldStatus = $newData[self::PROPERTY_WEBSITE]['value'] === $oldData[self::PROPERTY_WEBSITE]['value'] && isset($oldData[self::PROPERTY_WEBSITE]['verified']);
							 | 
						|
											$newData[self::PROPERTY_WEBSITE]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_WEBSITE]['verified'] : self::NOT_VERIFIED;
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if(!isset($newData[self::PROPERTY_EMAIL]['verified'])) {
							 | 
						|
											// keep old verification status if value didn't changed and an old value exists
							 | 
						|
											$keepOldStatus = $newData[self::PROPERTY_EMAIL]['value'] === $oldData[self::PROPERTY_EMAIL]['value'] && isset($oldData[self::PROPERTY_EMAIL]['verified']);
							 | 
						|
											$newData[self::PROPERTY_EMAIL]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_EMAIL]['verified'] : self::VERIFICATION_IN_PROGRESS;
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// reset verification status if a value from a previously verified data was changed
							 | 
						|
										if($twitterVerified &&
							 | 
						|
											$oldData[self::PROPERTY_TWITTER]['value'] !== $newData[self::PROPERTY_TWITTER]['value']
							 | 
						|
										) {
							 | 
						|
											$newData[self::PROPERTY_TWITTER]['verified'] = self::NOT_VERIFIED;
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if($websiteVerified &&
							 | 
						|
											$oldData[self::PROPERTY_WEBSITE]['value'] !== $newData[self::PROPERTY_WEBSITE]['value']
							 | 
						|
										) {
							 | 
						|
											$newData[self::PROPERTY_WEBSITE]['verified'] = self::NOT_VERIFIED;
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if($emailVerified &&
							 | 
						|
											$oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']
							 | 
						|
										) {
							 | 
						|
											$newData[self::PROPERTY_EMAIL]['verified'] = self::NOT_VERIFIED;
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										return $newData;
							 | 
						|
								
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									/**
							 | 
						|
									 * 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,
							 | 
						|
													'verified' => self::NOT_VERIFIED,
							 | 
						|
												],
							 | 
						|
											self::PROPERTY_ADDRESS =>
							 | 
						|
												[
							 | 
						|
													'value' => '',
							 | 
						|
													'scope' => self::VISIBILITY_PRIVATE,
							 | 
						|
													'verified' => self::NOT_VERIFIED,
							 | 
						|
												],
							 | 
						|
											self::PROPERTY_WEBSITE =>
							 | 
						|
												[
							 | 
						|
													'value' => '',
							 | 
						|
													'scope' => self::VISIBILITY_PRIVATE,
							 | 
						|
													'verified' => self::NOT_VERIFIED,
							 | 
						|
												],
							 | 
						|
											self::PROPERTY_EMAIL =>
							 | 
						|
												[
							 | 
						|
													'value' => $user->getEMailAddress(),
							 | 
						|
													'scope' => self::VISIBILITY_CONTACTS_ONLY,
							 | 
						|
													'verified' => self::NOT_VERIFIED,
							 | 
						|
												],
							 | 
						|
											self::PROPERTY_AVATAR =>
							 | 
						|
												[
							 | 
						|
													'scope' => self::VISIBILITY_CONTACTS_ONLY
							 | 
						|
												],
							 | 
						|
											self::PROPERTY_PHONE =>
							 | 
						|
												[
							 | 
						|
													'value' => '',
							 | 
						|
													'scope' => self::VISIBILITY_PRIVATE,
							 | 
						|
													'verified' => self::NOT_VERIFIED,
							 | 
						|
												],
							 | 
						|
											self::PROPERTY_TWITTER =>
							 | 
						|
												[
							 | 
						|
													'value' => '',
							 | 
						|
													'scope' => self::VISIBILITY_PRIVATE,
							 | 
						|
													'verified' => self::NOT_VERIFIED,
							 | 
						|
												],
							 | 
						|
										];
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									private function parseAccountData(IUser $user, $data): Account {
							 | 
						|
										$account = new Account($user);
							 | 
						|
										foreach($data as $property => $accountData) {
							 | 
						|
											$account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::VISIBILITY_PRIVATE, $accountData['verified'] ?? self::NOT_VERIFIED);
							 | 
						|
										}
							 | 
						|
										return $account;
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									public function getAccount(IUser $user): IAccount {
							 | 
						|
										return $this->parseAccountData($user, $this->getUser($user));
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
								}
							 |