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.

358 lines
11 KiB

3 years ago
  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\ILogger;
  37. use OCP\IUser;
  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 ILogger */
  60. private $logger;
  61. /**
  62. * AccountManager constructor.
  63. *
  64. * @param IDBConnection $connection
  65. * @param EventDispatcherInterface $eventDispatcher
  66. * @param IJobList $jobList
  67. */
  68. public function __construct(IDBConnection $connection,
  69. EventDispatcherInterface $eventDispatcher,
  70. IJobList $jobList,
  71. ILogger $logger) {
  72. $this->connection = $connection;
  73. $this->eventDispatcher = $eventDispatcher;
  74. $this->jobList = $jobList;
  75. $this->logger = $logger;
  76. }
  77. /**
  78. * update user record
  79. *
  80. * @param IUser $user
  81. * @param $data
  82. */
  83. public function updateUser(IUser $user, $data) {
  84. $userData = $this->getUser($user);
  85. $updated = true;
  86. if (empty($userData)) {
  87. $this->insertNewUser($user, $data);
  88. } elseif ($userData !== $data) {
  89. $data = $this->checkEmailVerification($userData, $data, $user);
  90. $data = $this->updateVerifyStatus($userData, $data);
  91. $this->updateExistingUser($user, $data);
  92. } else {
  93. // nothing needs to be done if new and old data set are the same
  94. $updated = false;
  95. }
  96. if ($updated) {
  97. $this->eventDispatcher->dispatch(
  98. 'OC\AccountManager::userUpdated',
  99. new GenericEvent($user, $data)
  100. );
  101. }
  102. }
  103. /**
  104. * delete user from accounts table
  105. *
  106. * @param IUser $user
  107. */
  108. public function deleteUser(IUser $user) {
  109. $uid = $user->getUID();
  110. $query = $this->connection->getQueryBuilder();
  111. $query->delete($this->table)
  112. ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
  113. ->execute();
  114. }
  115. /**
  116. * get stored data from a given user
  117. *
  118. * @param IUser $user
  119. * @return array
  120. */
  121. public function getUser(IUser $user) {
  122. $uid = $user->getUID();
  123. $query = $this->connection->getQueryBuilder();
  124. $query->select('data')
  125. ->from($this->table)
  126. ->where($query->expr()->eq('uid', $query->createParameter('uid')))
  127. ->setParameter('uid', $uid);
  128. $result = $query->execute();
  129. $accountData = $result->fetchAll();
  130. $result->closeCursor();
  131. if (empty($accountData)) {
  132. $userData = $this->buildDefaultUserRecord($user);
  133. $this->insertNewUser($user, $userData);
  134. return $userData;
  135. }
  136. $userDataArray = json_decode($accountData[0]['data'], true);
  137. $jsonError = json_last_error();
  138. if ($userDataArray === null || $userDataArray === [] || $jsonError !== JSON_ERROR_NONE) {
  139. $this->logger->critical("User data of $uid contained invalid JSON (error $jsonError), hence falling back to a default user record");
  140. return $this->buildDefaultUserRecord($user);
  141. }
  142. $userDataArray = $this->addMissingDefaultValues($userDataArray);
  143. return $userDataArray;
  144. }
  145. /**
  146. * check if we need to ask the server for email verification, if yes we create a cronjob
  147. *
  148. * @param $oldData
  149. * @param $newData
  150. * @param IUser $user
  151. * @return array
  152. */
  153. protected function checkEmailVerification($oldData, $newData, IUser $user) {
  154. if ($oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']) {
  155. $this->jobList->add(VerifyUserData::class,
  156. [
  157. 'verificationCode' => '',
  158. 'data' => $newData[self::PROPERTY_EMAIL]['value'],
  159. 'type' => self::PROPERTY_EMAIL,
  160. 'uid' => $user->getUID(),
  161. 'try' => 0,
  162. 'lastRun' => time()
  163. ]
  164. );
  165. $newData[AccountManager::PROPERTY_EMAIL]['verified'] = AccountManager::VERIFICATION_IN_PROGRESS;
  166. }
  167. return $newData;
  168. }
  169. /**
  170. * make sure that all expected data are set
  171. *
  172. * @param array $userData
  173. * @return array
  174. */
  175. protected function addMissingDefaultValues(array $userData) {
  176. foreach ($userData as $key => $value) {
  177. if (!isset($userData[$key]['verified'])) {
  178. $userData[$key]['verified'] = self::NOT_VERIFIED;
  179. }
  180. }
  181. return $userData;
  182. }
  183. /**
  184. * reset verification status if personal data changed
  185. *
  186. * @param array $oldData
  187. * @param array $newData
  188. * @return array
  189. */
  190. protected function updateVerifyStatus($oldData, $newData) {
  191. // which account was already verified successfully?
  192. $twitterVerified = isset($oldData[self::PROPERTY_TWITTER]['verified']) && $oldData[self::PROPERTY_TWITTER]['verified'] === self::VERIFIED;
  193. $websiteVerified = isset($oldData[self::PROPERTY_WEBSITE]['verified']) && $oldData[self::PROPERTY_WEBSITE]['verified'] === self::VERIFIED;
  194. $emailVerified = isset($oldData[self::PROPERTY_EMAIL]['verified']) && $oldData[self::PROPERTY_EMAIL]['verified'] === self::VERIFIED;
  195. // keep old verification status if we don't have a new one
  196. if (!isset($newData[self::PROPERTY_TWITTER]['verified'])) {
  197. // keep old verification status if value didn't changed and an old value exists
  198. $keepOldStatus = $newData[self::PROPERTY_TWITTER]['value'] === $oldData[self::PROPERTY_TWITTER]['value'] && isset($oldData[self::PROPERTY_TWITTER]['verified']);
  199. $newData[self::PROPERTY_TWITTER]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_TWITTER]['verified'] : self::NOT_VERIFIED;
  200. }
  201. if (!isset($newData[self::PROPERTY_WEBSITE]['verified'])) {
  202. // keep old verification status if value didn't changed and an old value exists
  203. $keepOldStatus = $newData[self::PROPERTY_WEBSITE]['value'] === $oldData[self::PROPERTY_WEBSITE]['value'] && isset($oldData[self::PROPERTY_WEBSITE]['verified']);
  204. $newData[self::PROPERTY_WEBSITE]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_WEBSITE]['verified'] : self::NOT_VERIFIED;
  205. }
  206. if (!isset($newData[self::PROPERTY_EMAIL]['verified'])) {
  207. // keep old verification status if value didn't changed and an old value exists
  208. $keepOldStatus = $newData[self::PROPERTY_EMAIL]['value'] === $oldData[self::PROPERTY_EMAIL]['value'] && isset($oldData[self::PROPERTY_EMAIL]['verified']);
  209. $newData[self::PROPERTY_EMAIL]['verified'] = $keepOldStatus ? $oldData[self::PROPERTY_EMAIL]['verified'] : self::VERIFICATION_IN_PROGRESS;
  210. }
  211. // reset verification status if a value from a previously verified data was changed
  212. if ($twitterVerified &&
  213. $oldData[self::PROPERTY_TWITTER]['value'] !== $newData[self::PROPERTY_TWITTER]['value']
  214. ) {
  215. $newData[self::PROPERTY_TWITTER]['verified'] = self::NOT_VERIFIED;
  216. }
  217. if ($websiteVerified &&
  218. $oldData[self::PROPERTY_WEBSITE]['value'] !== $newData[self::PROPERTY_WEBSITE]['value']
  219. ) {
  220. $newData[self::PROPERTY_WEBSITE]['verified'] = self::NOT_VERIFIED;
  221. }
  222. if ($emailVerified &&
  223. $oldData[self::PROPERTY_EMAIL]['value'] !== $newData[self::PROPERTY_EMAIL]['value']
  224. ) {
  225. $newData[self::PROPERTY_EMAIL]['verified'] = self::NOT_VERIFIED;
  226. }
  227. return $newData;
  228. }
  229. /**
  230. * add new user to accounts table
  231. *
  232. * @param IUser $user
  233. * @param array $data
  234. */
  235. protected function insertNewUser(IUser $user, $data) {
  236. $uid = $user->getUID();
  237. $jsonEncodedData = json_encode($data);
  238. $query = $this->connection->getQueryBuilder();
  239. $query->insert($this->table)
  240. ->values(
  241. [
  242. 'uid' => $query->createNamedParameter($uid),
  243. 'data' => $query->createNamedParameter($jsonEncodedData),
  244. ]
  245. )
  246. ->execute();
  247. }
  248. /**
  249. * update existing user in accounts table
  250. *
  251. * @param IUser $user
  252. * @param array $data
  253. */
  254. protected function updateExistingUser(IUser $user, $data) {
  255. $uid = $user->getUID();
  256. $jsonEncodedData = json_encode($data);
  257. $query = $this->connection->getQueryBuilder();
  258. $query->update($this->table)
  259. ->set('data', $query->createNamedParameter($jsonEncodedData))
  260. ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
  261. ->execute();
  262. }
  263. /**
  264. * build default user record in case not data set exists yet
  265. *
  266. * @param IUser $user
  267. * @return array
  268. */
  269. protected function buildDefaultUserRecord(IUser $user) {
  270. return [
  271. self::PROPERTY_DISPLAYNAME =>
  272. [
  273. 'value' => $user->getDisplayName(),
  274. 'scope' => self::VISIBILITY_CONTACTS_ONLY,
  275. 'verified' => self::NOT_VERIFIED,
  276. ],
  277. self::PROPERTY_ADDRESS =>
  278. [
  279. 'value' => '',
  280. 'scope' => self::VISIBILITY_PRIVATE,
  281. 'verified' => self::NOT_VERIFIED,
  282. ],
  283. self::PROPERTY_WEBSITE =>
  284. [
  285. 'value' => '',
  286. 'scope' => self::VISIBILITY_PRIVATE,
  287. 'verified' => self::NOT_VERIFIED,
  288. ],
  289. self::PROPERTY_EMAIL =>
  290. [
  291. 'value' => $user->getEMailAddress(),
  292. 'scope' => self::VISIBILITY_CONTACTS_ONLY,
  293. 'verified' => self::NOT_VERIFIED,
  294. ],
  295. self::PROPERTY_AVATAR =>
  296. [
  297. 'scope' => self::VISIBILITY_CONTACTS_ONLY
  298. ],
  299. self::PROPERTY_PHONE =>
  300. [
  301. 'value' => '',
  302. 'scope' => self::VISIBILITY_PRIVATE,
  303. 'verified' => self::NOT_VERIFIED,
  304. ],
  305. self::PROPERTY_TWITTER =>
  306. [
  307. 'value' => '',
  308. 'scope' => self::VISIBILITY_PRIVATE,
  309. 'verified' => self::NOT_VERIFIED,
  310. ],
  311. ];
  312. }
  313. private function parseAccountData(IUser $user, $data): Account {
  314. $account = new Account($user);
  315. foreach ($data as $property => $accountData) {
  316. $account->setProperty($property, $accountData['value'] ?? '', $accountData['scope'] ?? self::VISIBILITY_PRIVATE, $accountData['verified'] ?? self::NOT_VERIFIED);
  317. }
  318. return $account;
  319. }
  320. public function getAccount(IUser $user): IAccount {
  321. return $this->parseAccountData($user, $this->getUser($user));
  322. }
  323. }