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.

351 lines
11 KiB

  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. * @copyright Copyright (c) 2016, Björn Schießle
  5. *
  6. * @author Bjoern Schiessle <bjoern@schiessle.org>
  7. * @author Björn Schießle <bjoern@schiessle.org>
  8. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  9. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  10. * @author Joas Schilling <coding@schilljs.com>
  11. * @author Julius Härtl <jus@bitgrid.net>
  12. * @author Morris Jobke <hey@morrisjobke.de>
  13. * @author Roeland Jago Douma <roeland@famdouma.nl>
  14. *
  15. * @license AGPL-3.0
  16. *
  17. * This code is free software: you can redistribute it and/or modify
  18. * it under the terms of the GNU Affero General Public License, version 3,
  19. * as published by the Free Software Foundation.
  20. *
  21. * This program is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. * GNU Affero General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU Affero General Public License, version 3,
  27. * along with this program. If not, see <http://www.gnu.org/licenses/>
  28. *
  29. */
  30. namespace OC\Accounts;
  31. use OCA\Settings\BackgroundJobs\VerifyUserData;
  32. use OCP\Accounts\IAccount;
  33. use OCP\Accounts\IAccountManager;
  34. use OCP\BackgroundJob\IJobList;
  35. use OCP\IDBConnection;
  36. use OCP\IUser;
  37. use Psr\Log\LoggerInterface;
  38. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  39. use Symfony\Component\EventDispatcher\GenericEvent;
  40. use function json_decode;
  41. use function json_last_error;
  42. /**
  43. * Class AccountManager
  44. *
  45. * Manage system accounts table
  46. *
  47. * @group DB
  48. * @package OC\Accounts
  49. */
  50. class AccountManager implements IAccountManager {
  51. /** @var IDBConnection database connection */
  52. private $connection;
  53. /** @var string table name */
  54. private $table = 'accounts';
  55. /** @var EventDispatcherInterface */
  56. private $eventDispatcher;
  57. /** @var IJobList */
  58. private $jobList;
  59. /** @var LoggerInterface */
  60. private $logger;
  61. public function __construct(IDBConnection $connection,
  62. EventDispatcherInterface $eventDispatcher,
  63. IJobList $jobList,
  64. LoggerInterface $logger) {
  65. $this->connection = $connection;
  66. $this->eventDispatcher = $eventDispatcher;
  67. $this->jobList = $jobList;
  68. $this->logger = $logger;
  69. }
  70. /**
  71. * update user record
  72. *
  73. * @param IUser $user
  74. * @param $data
  75. */
  76. public function updateUser(IUser $user, $data) {
  77. $userData = $this->getUser($user);
  78. $updated = true;
  79. if (empty($userData)) {
  80. $this->insertNewUser($user, $data);
  81. } elseif ($userData !== $data) {
  82. $data = $this->checkEmailVerification($userData, $data, $user);
  83. $data = $this->updateVerifyStatus($userData, $data);
  84. $this->updateExistingUser($user, $data);
  85. } else {
  86. // nothing needs to be done if new and old data set are the same
  87. $updated = false;
  88. }
  89. if ($updated) {
  90. $this->eventDispatcher->dispatch(
  91. 'OC\AccountManager::userUpdated',
  92. new GenericEvent($user, $data)
  93. );
  94. }
  95. }
  96. /**
  97. * delete user from accounts table
  98. *
  99. * @param IUser $user
  100. */
  101. public function deleteUser(IUser $user) {
  102. $uid = $user->getUID();
  103. $query = $this->connection->getQueryBuilder();
  104. $query->delete($this->table)
  105. ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
  106. ->execute();
  107. }
  108. /**
  109. * get stored data from a given user
  110. *
  111. * @param IUser $user
  112. * @return array
  113. */
  114. public function getUser(IUser $user) {
  115. $uid = $user->getUID();
  116. $query = $this->connection->getQueryBuilder();
  117. $query->select('data')
  118. ->from($this->table)
  119. ->where($query->expr()->eq('uid', $query->createParameter('uid')))
  120. ->setParameter('uid', $uid);
  121. $result = $query->execute();
  122. $accountData = $result->fetchAll();
  123. $result->closeCursor();
  124. if (empty($accountData)) {
  125. $userData = $this->buildDefaultUserRecord($user);
  126. $this->insertNewUser($user, $userData);
  127. return $userData;
  128. }
  129. $userDataArray = json_decode($accountData[0]['data'], true);
  130. $jsonError = json_last_error();
  131. if ($userDataArray === null || $userDataArray === [] || $jsonError !== JSON_ERROR_NONE) {
  132. $this->logger->critical("User data of $uid contained invalid JSON (error $jsonError), hence falling back to a default user record");
  133. return $this->buildDefaultUserRecord($user);
  134. }
  135. $userDataArray = $this->addMissingDefaultValues($userDataArray);
  136. return $userDataArray;
  137. }
  138. /**
  139. * check if we need to ask the server for email verification, if yes we create a cronjob
  140. *
  141. * @param $oldData
  142. * @param $newData
  143. * @param IUser $user
  144. * @return array
  145. */
  146. protected function checkEmailVerification($oldData, $newData, IUser $user) {
  147. if ($oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']) {
  148. $this->jobList->add(VerifyUserData::class,
  149. [
  150. 'verificationCode' => '',
  151. 'data' => $newData[self::PROPERTY_EMAIL]['value'],
  152. 'type' => self::PROPERTY_EMAIL,
  153. 'uid' => $user->getUID(),
  154. 'try' => 0,
  155. 'lastRun' => time()
  156. ]
  157. );
  158. $newData[AccountManager::PROPERTY_EMAIL]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
  159. }
  160. return $newData;
  161. }
  162. /**
  163. * make sure that all expected data are set
  164. *
  165. * @param array $userData
  166. * @return array
  167. */
  168. protected function addMissingDefaultValues(array $userData) {
  169. foreach ($userData as $key => $value) {
  170. if (!isset($userData[$key]['verified'])) {
  171. $userData[$key]['verified'] = self::NOT_VERIFIED;
  172. }
  173. }
  174. return $userData;
  175. }
  176. /**
  177. * reset verification status if personal data changed
  178. *
  179. * @param array $oldData
  180. * @param array $newData
  181. * @return array
  182. */
  183. protected function updateVerifyStatus($oldData, $newData) {
  184. // which account was already verified successfully?
  185. $twitterVerified = isset($oldData[self::PROPERTY_TWITTER]['verified']) && $oldData[self::PROPERTY_TWITTER]['verified'] === self::VERIFIED;
  186. $websiteVerified = isset($oldData[self::PROPERTY_WEBSITE]['verified']) && $oldData[self::PROPERTY_WEBSITE]['verified'] === self::VERIFIED;
  187. $emailVerified = isset($oldData[self::PROPERTY_EMAIL]['verified']) && $oldData[self::PROPERTY_EMAIL]['verified'] === self::VERIFIED;
  188. // keep old verification status if we don't have a new one
  189. if (!isset($newData[self::PROPERTY_TWITTER]['verified'])) {
  190. // keep old verification status if value didn't changed and an old value exists
  191. $keepOldStatus = $newData[self::PROPERTY_TWITTER]['value'] === $oldData[self::PROPERTY_TWITTER]['value'] && isset($oldData[self::PROPERTY_TWITTER]['verified']);
  192. $newData[self::PROPERTY_TWITTER]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_TWITTER]['verified'] : self::NOT_VERIFIED;
  193. }
  194. if (!isset($newData[self::PROPERTY_WEBSITE]['verified'])) {
  195. // keep old verification status if value didn't changed and an old value exists
  196. $keepOldStatus = $newData[self::PROPERTY_WEBSITE]['value'] === $oldData[self::PROPERTY_WEBSITE]['value'] && isset($oldData[self::PROPERTY_WEBSITE]['verified']);
  197. $newData[self::PROPERTY_WEBSITE]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_WEBSITE]['verified'] : self::NOT_VERIFIED;
  198. }
  199. if (!isset($newData[self::PROPERTY_EMAIL]['verified'])) {
  200. // keep old verification status if value didn't changed and an old value exists
  201. $keepOldStatus = $newData[self::PROPERTY_EMAIL]['value'] === $oldData[self::PROPERTY_EMAIL]['value'] && isset($oldData[self::PROPERTY_EMAIL]['verified']);
  202. $newData[self::PROPERTY_EMAIL]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_EMAIL]['verified'] : self::VERIFICATION_IN_PROGRESS;
  203. }
  204. // reset verification status if a value from a previously verified data was changed
  205. if ($twitterVerified &&
  206. $oldData[self::PROPERTY_TWITTER]['value'] !== $newData[self::PROPERTY_TWITTER]['value']
  207. ) {
  208. $newData[self::PROPERTY_TWITTER]['verified'] = self::NOT_VERIFIED;
  209. }
  210. if ($websiteVerified &&
  211. $oldData[self::PROPERTY_WEBSITE]['value'] !== $newData[self::PROPERTY_WEBSITE]['value']
  212. ) {
  213. $newData[self::PROPERTY_WEBSITE]['verified'] = self::NOT_VERIFIED;
  214. }
  215. if ($emailVerified &&
  216. $oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']
  217. ) {
  218. $newData[self::PROPERTY_EMAIL]['verified'] = self::NOT_VERIFIED;
  219. }
  220. return $newData;
  221. }
  222. /**
  223. * add new user to accounts table
  224. *
  225. * @param IUser $user
  226. * @param array $data
  227. */
  228. protected function insertNewUser(IUser $user, $data) {
  229. $uid = $user->getUID();
  230. $jsonEncodedData = json_encode($data);
  231. $query = $this->connection->getQueryBuilder();
  232. $query->insert($this->table)
  233. ->values(
  234. [
  235. 'uid' => $query->createNamedParameter($uid),
  236. 'data' => $query->createNamedParameter($jsonEncodedData),
  237. ]
  238. )
  239. ->execute();
  240. }
  241. /**
  242. * update existing user in accounts table
  243. *
  244. * @param IUser $user
  245. * @param array $data
  246. */
  247. protected function updateExistingUser(IUser $user, $data) {
  248. $uid = $user->getUID();
  249. $jsonEncodedData = json_encode($data);
  250. $query = $this->connection->getQueryBuilder();
  251. $query->update($this->table)
  252. ->set('data', $query->createNamedParameter($jsonEncodedData))
  253. ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
  254. ->execute();
  255. }
  256. /**
  257. * build default user record in case not data set exists yet
  258. *
  259. * @param IUser $user
  260. * @return array
  261. */
  262. protected function buildDefaultUserRecord(IUser $user) {
  263. return [
  264. self::PROPERTY_DISPLAYNAME =>
  265. [
  266. 'value' => $user->getDisplayName(),
  267. 'scope' => self::VISIBILITY_CONTACTS_ONLY,
  268. 'verified' => self::NOT_VERIFIED,
  269. ],
  270. self::PROPERTY_ADDRESS =>
  271. [
  272. 'value' => '',
  273. 'scope' => self::VISIBILITY_PRIVATE,
  274. 'verified' => self::NOT_VERIFIED,
  275. ],
  276. self::PROPERTY_WEBSITE =>
  277. [
  278. 'value' => '',
  279. 'scope' => self::VISIBILITY_PRIVATE,
  280. 'verified' => self::NOT_VERIFIED,
  281. ],
  282. self::PROPERTY_EMAIL =>
  283. [
  284. 'value' => $user->getEMailAddress(),
  285. 'scope' => self::VISIBILITY_CONTACTS_ONLY,
  286. 'verified' => self::NOT_VERIFIED,
  287. ],
  288. self::PROPERTY_AVATAR =>
  289. [
  290. 'scope' => self::VISIBILITY_CONTACTS_ONLY
  291. ],
  292. self::PROPERTY_PHONE =>
  293. [
  294. 'value' => '',
  295. 'scope' => self::VISIBILITY_PRIVATE,
  296. 'verified' => self::NOT_VERIFIED,
  297. ],
  298. self::PROPERTY_TWITTER =>
  299. [
  300. 'value' => '',
  301. 'scope' => self::VISIBILITY_PRIVATE,
  302. 'verified' => self::NOT_VERIFIED,
  303. ],
  304. ];
  305. }
  306. private function parseAccountData(IUser $user, $data): Account {
  307. $account = new Account($user);
  308. foreach ($data as $property => $accountData) {
  309. $account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::VISIBILITY_PRIVATE, $accountData['verified'] ?? self::NOT_VERIFIED);
  310. }
  311. return $account;
  312. }
  313. public function getAccount(IUser $user): IAccount {
  314. return $this->parseAccountData($user, $this->getUser($user));
  315. }
  316. }