Passed
Push — master ( 411754...4670c5 )
by Joas
34:20 queued 20:57
created
apps/settings/lib/Controller/ChangePasswordController.php 1 patch
Indentation   +217 added lines, -217 removed lines patch added patch discarded remove patch
@@ -49,235 +49,235 @@
 block discarded – undo
49 49
 use OCP\IUserSession;
50 50
 
51 51
 class ChangePasswordController extends Controller {
52
-	private ?string $userId;
53
-	private IUserManager $userManager;
54
-	private IL10N $l;
55
-	private GroupManager $groupManager;
56
-	private Session $userSession;
57
-	private IAppManager $appManager;
52
+    private ?string $userId;
53
+    private IUserManager $userManager;
54
+    private IL10N $l;
55
+    private GroupManager $groupManager;
56
+    private Session $userSession;
57
+    private IAppManager $appManager;
58 58
 
59
-	public function __construct(string $appName,
60
-								IRequest $request,
61
-								?string $userId,
62
-								IUserManager $userManager,
63
-								IUserSession $userSession,
64
-								IGroupManager $groupManager,
65
-								IAppManager $appManager,
66
-								IL10N $l) {
67
-		parent::__construct($appName, $request);
59
+    public function __construct(string $appName,
60
+                                IRequest $request,
61
+                                ?string $userId,
62
+                                IUserManager $userManager,
63
+                                IUserSession $userSession,
64
+                                IGroupManager $groupManager,
65
+                                IAppManager $appManager,
66
+                                IL10N $l) {
67
+        parent::__construct($appName, $request);
68 68
 
69
-		$this->userId = $userId;
70
-		$this->userManager = $userManager;
71
-		$this->userSession = $userSession;
72
-		$this->groupManager = $groupManager;
73
-		$this->appManager = $appManager;
74
-		$this->l = $l;
75
-	}
69
+        $this->userId = $userId;
70
+        $this->userManager = $userManager;
71
+        $this->userSession = $userSession;
72
+        $this->groupManager = $groupManager;
73
+        $this->appManager = $appManager;
74
+        $this->l = $l;
75
+    }
76 76
 
77
-	/**
78
-	 * @NoAdminRequired
79
-	 * @NoSubAdminRequired
80
-	 * @BruteForceProtection(action=changePersonalPassword)
81
-	 */
82
-	public function changePersonalPassword(string $oldpassword = '', string $newpassword = null): JSONResponse {
83
-		$loginName = $this->userSession->getLoginName();
84
-		/** @var IUser $user */
85
-		$user = $this->userManager->checkPassword($loginName, $oldpassword);
86
-		if ($user === false) {
87
-			$response = new JSONResponse([
88
-				'status' => 'error',
89
-				'data' => [
90
-					'message' => $this->l->t('Wrong password'),
91
-				],
92
-			]);
93
-			$response->throttle();
94
-			return $response;
95
-		}
77
+    /**
78
+     * @NoAdminRequired
79
+     * @NoSubAdminRequired
80
+     * @BruteForceProtection(action=changePersonalPassword)
81
+     */
82
+    public function changePersonalPassword(string $oldpassword = '', string $newpassword = null): JSONResponse {
83
+        $loginName = $this->userSession->getLoginName();
84
+        /** @var IUser $user */
85
+        $user = $this->userManager->checkPassword($loginName, $oldpassword);
86
+        if ($user === false) {
87
+            $response = new JSONResponse([
88
+                'status' => 'error',
89
+                'data' => [
90
+                    'message' => $this->l->t('Wrong password'),
91
+                ],
92
+            ]);
93
+            $response->throttle();
94
+            return $response;
95
+        }
96 96
 
97
-		try {
98
-			if ($newpassword === null || strlen($newpassword) > IUserManager::MAX_PASSWORD_LENGTH || $user->setPassword($newpassword) === false) {
99
-				return new JSONResponse([
100
-					'status' => 'error',
101
-					'data' => [
102
-						'message' => $this->l->t('Unable to change personal password'),
103
-					],
104
-				]);
105
-			}
106
-			// password policy app throws exception
107
-		} catch (HintException $e) {
108
-			return new JSONResponse([
109
-				'status' => 'error',
110
-				'data' => [
111
-					'message' => $e->getHint(),
112
-				],
113
-			]);
114
-		}
97
+        try {
98
+            if ($newpassword === null || strlen($newpassword) > IUserManager::MAX_PASSWORD_LENGTH || $user->setPassword($newpassword) === false) {
99
+                return new JSONResponse([
100
+                    'status' => 'error',
101
+                    'data' => [
102
+                        'message' => $this->l->t('Unable to change personal password'),
103
+                    ],
104
+                ]);
105
+            }
106
+            // password policy app throws exception
107
+        } catch (HintException $e) {
108
+            return new JSONResponse([
109
+                'status' => 'error',
110
+                'data' => [
111
+                    'message' => $e->getHint(),
112
+                ],
113
+            ]);
114
+        }
115 115
 
116
-		$this->userSession->updateSessionTokenPassword($newpassword);
116
+        $this->userSession->updateSessionTokenPassword($newpassword);
117 117
 
118
-		return new JSONResponse([
119
-			'status' => 'success',
120
-			'data' => [
121
-				'message' => $this->l->t('Saved'),
122
-			],
123
-		]);
124
-	}
118
+        return new JSONResponse([
119
+            'status' => 'success',
120
+            'data' => [
121
+                'message' => $this->l->t('Saved'),
122
+            ],
123
+        ]);
124
+    }
125 125
 
126
-	/**
127
-	 * @NoAdminRequired
128
-	 * @PasswordConfirmationRequired
129
-	 */
130
-	public function changeUserPassword(string $username = null, string $password = null, string $recoveryPassword = null): JSONResponse {
131
-		if ($username === null) {
132
-			return new JSONResponse([
133
-				'status' => 'error',
134
-				'data' => [
135
-					'message' => $this->l->t('No user supplied'),
136
-				],
137
-			]);
138
-		}
126
+    /**
127
+     * @NoAdminRequired
128
+     * @PasswordConfirmationRequired
129
+     */
130
+    public function changeUserPassword(string $username = null, string $password = null, string $recoveryPassword = null): JSONResponse {
131
+        if ($username === null) {
132
+            return new JSONResponse([
133
+                'status' => 'error',
134
+                'data' => [
135
+                    'message' => $this->l->t('No user supplied'),
136
+                ],
137
+            ]);
138
+        }
139 139
 
140
-		if ($password === null) {
141
-			return new JSONResponse([
142
-				'status' => 'error',
143
-				'data' => [
144
-					'message' => $this->l->t('Unable to change password'),
145
-				],
146
-			]);
147
-		}
140
+        if ($password === null) {
141
+            return new JSONResponse([
142
+                'status' => 'error',
143
+                'data' => [
144
+                    'message' => $this->l->t('Unable to change password'),
145
+                ],
146
+            ]);
147
+        }
148 148
 
149
-		if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
150
-			return new JSONResponse([
151
-				'status' => 'error',
152
-				'data' => [
153
-					'message' => $this->l->t('Unable to change password. Password too long.'),
154
-				],
155
-			]);
156
-		}
149
+        if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
150
+            return new JSONResponse([
151
+                'status' => 'error',
152
+                'data' => [
153
+                    'message' => $this->l->t('Unable to change password. Password too long.'),
154
+                ],
155
+            ]);
156
+        }
157 157
 
158
-		$currentUser = $this->userSession->getUser();
159
-		$targetUser = $this->userManager->get($username);
160
-		if ($currentUser === null || $targetUser === null ||
161
-			!($this->groupManager->isAdmin($this->userId) ||
162
-			 $this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $targetUser))
163
-		) {
164
-			return new JSONResponse([
165
-				'status' => 'error',
166
-				'data' => [
167
-					'message' => $this->l->t('Authentication error'),
168
-				],
169
-			]);
170
-		}
158
+        $currentUser = $this->userSession->getUser();
159
+        $targetUser = $this->userManager->get($username);
160
+        if ($currentUser === null || $targetUser === null ||
161
+            !($this->groupManager->isAdmin($this->userId) ||
162
+             $this->groupManager->getSubAdmin()->isUserAccessible($currentUser, $targetUser))
163
+        ) {
164
+            return new JSONResponse([
165
+                'status' => 'error',
166
+                'data' => [
167
+                    'message' => $this->l->t('Authentication error'),
168
+                ],
169
+            ]);
170
+        }
171 171
 
172
-		if ($this->appManager->isEnabledForUser('encryption')) {
173
-			//handle the recovery case
174
-			$crypt = new \OCA\Encryption\Crypto\Crypt(
175
-				\OC::$server->getLogger(),
176
-				\OC::$server->getUserSession(),
177
-				\OC::$server->getConfig(),
178
-				\OC::$server->getL10N('encryption'));
179
-			$keyStorage = \OC::$server->getEncryptionKeyStorage();
180
-			$util = new \OCA\Encryption\Util(
181
-				new \OC\Files\View(),
182
-				$crypt,
183
-				\OC::$server->getLogger(),
184
-				\OC::$server->getUserSession(),
185
-				\OC::$server->getConfig(),
186
-				\OC::$server->getUserManager());
187
-			$keyManager = new \OCA\Encryption\KeyManager(
188
-				$keyStorage,
189
-				$crypt,
190
-				\OC::$server->getConfig(),
191
-				\OC::$server->getUserSession(),
192
-				new \OCA\Encryption\Session(\OC::$server->getSession()),
193
-				\OC::$server->getLogger(),
194
-				$util,
195
-				\OC::$server->getLockingProvider()
196
-			);
197
-			$recovery = new \OCA\Encryption\Recovery(
198
-				\OC::$server->getUserSession(),
199
-				$crypt,
200
-				$keyManager,
201
-				\OC::$server->getConfig(),
202
-				\OC::$server->getEncryptionFilesHelper(),
203
-				new \OC\Files\View());
204
-			$recoveryAdminEnabled = $recovery->isRecoveryKeyEnabled();
172
+        if ($this->appManager->isEnabledForUser('encryption')) {
173
+            //handle the recovery case
174
+            $crypt = new \OCA\Encryption\Crypto\Crypt(
175
+                \OC::$server->getLogger(),
176
+                \OC::$server->getUserSession(),
177
+                \OC::$server->getConfig(),
178
+                \OC::$server->getL10N('encryption'));
179
+            $keyStorage = \OC::$server->getEncryptionKeyStorage();
180
+            $util = new \OCA\Encryption\Util(
181
+                new \OC\Files\View(),
182
+                $crypt,
183
+                \OC::$server->getLogger(),
184
+                \OC::$server->getUserSession(),
185
+                \OC::$server->getConfig(),
186
+                \OC::$server->getUserManager());
187
+            $keyManager = new \OCA\Encryption\KeyManager(
188
+                $keyStorage,
189
+                $crypt,
190
+                \OC::$server->getConfig(),
191
+                \OC::$server->getUserSession(),
192
+                new \OCA\Encryption\Session(\OC::$server->getSession()),
193
+                \OC::$server->getLogger(),
194
+                $util,
195
+                \OC::$server->getLockingProvider()
196
+            );
197
+            $recovery = new \OCA\Encryption\Recovery(
198
+                \OC::$server->getUserSession(),
199
+                $crypt,
200
+                $keyManager,
201
+                \OC::$server->getConfig(),
202
+                \OC::$server->getEncryptionFilesHelper(),
203
+                new \OC\Files\View());
204
+            $recoveryAdminEnabled = $recovery->isRecoveryKeyEnabled();
205 205
 
206
-			$validRecoveryPassword = false;
207
-			$recoveryEnabledForUser = false;
208
-			if ($recoveryAdminEnabled) {
209
-				$validRecoveryPassword = $keyManager->checkRecoveryPassword($recoveryPassword);
210
-				$recoveryEnabledForUser = $recovery->isRecoveryEnabledForUser($username);
211
-			}
206
+            $validRecoveryPassword = false;
207
+            $recoveryEnabledForUser = false;
208
+            if ($recoveryAdminEnabled) {
209
+                $validRecoveryPassword = $keyManager->checkRecoveryPassword($recoveryPassword);
210
+                $recoveryEnabledForUser = $recovery->isRecoveryEnabledForUser($username);
211
+            }
212 212
 
213
-			if ($recoveryEnabledForUser && $recoveryPassword === '') {
214
-				return new JSONResponse([
215
-					'status' => 'error',
216
-					'data' => [
217
-						'message' => $this->l->t('Please provide an admin recovery password; otherwise, all user data will be lost.'),
218
-					]
219
-				]);
220
-			} elseif ($recoveryEnabledForUser && ! $validRecoveryPassword) {
221
-				return new JSONResponse([
222
-					'status' => 'error',
223
-					'data' => [
224
-						'message' => $this->l->t('Wrong admin recovery password. Please check the password and try again.'),
225
-					]
226
-				]);
227
-			} else { // now we know that everything is fine regarding the recovery password, let's try to change the password
228
-				try {
229
-					$result = $targetUser->setPassword($password, $recoveryPassword);
230
-					// password policy app throws exception
231
-				} catch (HintException $e) {
232
-					return new JSONResponse([
233
-						'status' => 'error',
234
-						'data' => [
235
-							'message' => $e->getHint(),
236
-						],
237
-					]);
238
-				}
239
-				if (!$result && $recoveryEnabledForUser) {
240
-					return new JSONResponse([
241
-						'status' => 'error',
242
-						'data' => [
243
-							'message' => $this->l->t('Backend does not support password change, but the user\'s encryption key was updated.'),
244
-						]
245
-					]);
246
-				} elseif (!$result && !$recoveryEnabledForUser) {
247
-					return new JSONResponse([
248
-						'status' => 'error',
249
-						'data' => [
250
-							'message' => $this->l->t('Unable to change password'),
251
-						]
252
-					]);
253
-				}
254
-			}
255
-		} else {
256
-			try {
257
-				if ($targetUser->setPassword($password) === false) {
258
-					return new JSONResponse([
259
-						'status' => 'error',
260
-						'data' => [
261
-							'message' => $this->l->t('Unable to change password'),
262
-						],
263
-					]);
264
-				}
265
-				// password policy app throws exception
266
-			} catch (HintException $e) {
267
-				return new JSONResponse([
268
-					'status' => 'error',
269
-					'data' => [
270
-						'message' => $e->getHint(),
271
-					],
272
-				]);
273
-			}
274
-		}
213
+            if ($recoveryEnabledForUser && $recoveryPassword === '') {
214
+                return new JSONResponse([
215
+                    'status' => 'error',
216
+                    'data' => [
217
+                        'message' => $this->l->t('Please provide an admin recovery password; otherwise, all user data will be lost.'),
218
+                    ]
219
+                ]);
220
+            } elseif ($recoveryEnabledForUser && ! $validRecoveryPassword) {
221
+                return new JSONResponse([
222
+                    'status' => 'error',
223
+                    'data' => [
224
+                        'message' => $this->l->t('Wrong admin recovery password. Please check the password and try again.'),
225
+                    ]
226
+                ]);
227
+            } else { // now we know that everything is fine regarding the recovery password, let's try to change the password
228
+                try {
229
+                    $result = $targetUser->setPassword($password, $recoveryPassword);
230
+                    // password policy app throws exception
231
+                } catch (HintException $e) {
232
+                    return new JSONResponse([
233
+                        'status' => 'error',
234
+                        'data' => [
235
+                            'message' => $e->getHint(),
236
+                        ],
237
+                    ]);
238
+                }
239
+                if (!$result && $recoveryEnabledForUser) {
240
+                    return new JSONResponse([
241
+                        'status' => 'error',
242
+                        'data' => [
243
+                            'message' => $this->l->t('Backend does not support password change, but the user\'s encryption key was updated.'),
244
+                        ]
245
+                    ]);
246
+                } elseif (!$result && !$recoveryEnabledForUser) {
247
+                    return new JSONResponse([
248
+                        'status' => 'error',
249
+                        'data' => [
250
+                            'message' => $this->l->t('Unable to change password'),
251
+                        ]
252
+                    ]);
253
+                }
254
+            }
255
+        } else {
256
+            try {
257
+                if ($targetUser->setPassword($password) === false) {
258
+                    return new JSONResponse([
259
+                        'status' => 'error',
260
+                        'data' => [
261
+                            'message' => $this->l->t('Unable to change password'),
262
+                        ],
263
+                    ]);
264
+                }
265
+                // password policy app throws exception
266
+            } catch (HintException $e) {
267
+                return new JSONResponse([
268
+                    'status' => 'error',
269
+                    'data' => [
270
+                        'message' => $e->getHint(),
271
+                    ],
272
+                ]);
273
+            }
274
+        }
275 275
 
276
-		return new JSONResponse([
277
-			'status' => 'success',
278
-			'data' => [
279
-				'username' => $username,
280
-			],
281
-		]);
282
-	}
276
+        return new JSONResponse([
277
+            'status' => 'success',
278
+            'data' => [
279
+                'username' => $username,
280
+            ],
281
+        ]);
282
+    }
283 283
 }
Please login to merge, or discard this patch.
apps/provisioning_api/lib/Controller/UsersController.php 1 patch
Indentation   +1345 added lines, -1345 removed lines patch added patch discarded remove patch
@@ -78,1349 +78,1349 @@
 block discarded – undo
78 78
 
79 79
 class UsersController extends AUserData {
80 80
 
81
-	/** @var IURLGenerator */
82
-	protected $urlGenerator;
83
-	/** @var LoggerInterface */
84
-	private $logger;
85
-	/** @var IFactory */
86
-	protected $l10nFactory;
87
-	/** @var NewUserMailHelper */
88
-	private $newUserMailHelper;
89
-	/** @var ISecureRandom */
90
-	private $secureRandom;
91
-	/** @var RemoteWipe */
92
-	private $remoteWipe;
93
-	/** @var KnownUserService */
94
-	private $knownUserService;
95
-	/** @var IEventDispatcher */
96
-	private $eventDispatcher;
97
-
98
-	public function __construct(
99
-		string $appName,
100
-		IRequest $request,
101
-		IUserManager $userManager,
102
-		IConfig $config,
103
-		IGroupManager $groupManager,
104
-		IUserSession $userSession,
105
-		IAccountManager $accountManager,
106
-		IURLGenerator $urlGenerator,
107
-		LoggerInterface $logger,
108
-		IFactory $l10nFactory,
109
-		NewUserMailHelper $newUserMailHelper,
110
-		ISecureRandom $secureRandom,
111
-		RemoteWipe $remoteWipe,
112
-		KnownUserService $knownUserService,
113
-		IEventDispatcher $eventDispatcher
114
-	) {
115
-		parent::__construct(
116
-			$appName,
117
-			$request,
118
-			$userManager,
119
-			$config,
120
-			$groupManager,
121
-			$userSession,
122
-			$accountManager,
123
-			$l10nFactory
124
-		);
125
-
126
-		$this->urlGenerator = $urlGenerator;
127
-		$this->logger = $logger;
128
-		$this->l10nFactory = $l10nFactory;
129
-		$this->newUserMailHelper = $newUserMailHelper;
130
-		$this->secureRandom = $secureRandom;
131
-		$this->remoteWipe = $remoteWipe;
132
-		$this->knownUserService = $knownUserService;
133
-		$this->eventDispatcher = $eventDispatcher;
134
-	}
135
-
136
-	/**
137
-	 * @NoAdminRequired
138
-	 *
139
-	 * returns a list of users
140
-	 *
141
-	 * @param string $search
142
-	 * @param int $limit
143
-	 * @param int $offset
144
-	 * @return DataResponse
145
-	 */
146
-	public function getUsers(string $search = '', int $limit = null, int $offset = 0): DataResponse {
147
-		$user = $this->userSession->getUser();
148
-		$users = [];
149
-
150
-		// Admin? Or SubAdmin?
151
-		$uid = $user->getUID();
152
-		$subAdminManager = $this->groupManager->getSubAdmin();
153
-		if ($this->groupManager->isAdmin($uid)) {
154
-			$users = $this->userManager->search($search, $limit, $offset);
155
-		} elseif ($subAdminManager->isSubAdmin($user)) {
156
-			$subAdminOfGroups = $subAdminManager->getSubAdminsGroups($user);
157
-			foreach ($subAdminOfGroups as $key => $group) {
158
-				$subAdminOfGroups[$key] = $group->getGID();
159
-			}
160
-
161
-			$users = [];
162
-			foreach ($subAdminOfGroups as $group) {
163
-				$users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
164
-			}
165
-		}
166
-
167
-		$users = array_keys($users);
168
-
169
-		return new DataResponse([
170
-			'users' => $users
171
-		]);
172
-	}
173
-
174
-	/**
175
-	 * @NoAdminRequired
176
-	 *
177
-	 * returns a list of users and their data
178
-	 */
179
-	public function getUsersDetails(string $search = '', int $limit = null, int $offset = 0): DataResponse {
180
-		$currentUser = $this->userSession->getUser();
181
-		$users = [];
182
-
183
-		// Admin? Or SubAdmin?
184
-		$uid = $currentUser->getUID();
185
-		$subAdminManager = $this->groupManager->getSubAdmin();
186
-		if ($this->groupManager->isAdmin($uid)) {
187
-			$users = $this->userManager->search($search, $limit, $offset);
188
-			$users = array_keys($users);
189
-		} elseif ($subAdminManager->isSubAdmin($currentUser)) {
190
-			$subAdminOfGroups = $subAdminManager->getSubAdminsGroups($currentUser);
191
-			foreach ($subAdminOfGroups as $key => $group) {
192
-				$subAdminOfGroups[$key] = $group->getGID();
193
-			}
194
-
195
-			$users = [];
196
-			foreach ($subAdminOfGroups as $group) {
197
-				$users[] = array_keys($this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
198
-			}
199
-			$users = array_merge(...$users);
200
-		}
201
-
202
-		$usersDetails = [];
203
-		foreach ($users as $userId) {
204
-			$userId = (string) $userId;
205
-			$userData = $this->getUserData($userId);
206
-			// Do not insert empty entry
207
-			if (!empty($userData)) {
208
-				$usersDetails[$userId] = $userData;
209
-			} else {
210
-				// Logged user does not have permissions to see this user
211
-				// only showing its id
212
-				$usersDetails[$userId] = ['id' => $userId];
213
-			}
214
-		}
215
-
216
-		return new DataResponse([
217
-			'users' => $usersDetails
218
-		]);
219
-	}
220
-
221
-
222
-	/**
223
-	 * @NoAdminRequired
224
-	 * @NoSubAdminRequired
225
-	 *
226
-	 * @param string $location
227
-	 * @param array $search
228
-	 * @return DataResponse
229
-	 */
230
-	public function searchByPhoneNumbers(string $location, array $search): DataResponse {
231
-		$phoneUtil = PhoneNumberUtil::getInstance();
232
-
233
-		if ($phoneUtil->getCountryCodeForRegion($location) === 0) {
234
-			// Not a valid region code
235
-			return new DataResponse([], Http::STATUS_BAD_REQUEST);
236
-		}
237
-
238
-		/** @var IUser $user */
239
-		$user = $this->userSession->getUser();
240
-		$knownTo = $user->getUID();
241
-		$defaultPhoneRegion = $this->config->getSystemValueString('default_phone_region');
242
-
243
-		$normalizedNumberToKey = [];
244
-		foreach ($search as $key => $phoneNumbers) {
245
-			foreach ($phoneNumbers as $phone) {
246
-				try {
247
-					$phoneNumber = $phoneUtil->parse($phone, $location);
248
-					if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
249
-						$normalizedNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
250
-						$normalizedNumberToKey[$normalizedNumber] = (string) $key;
251
-					}
252
-				} catch (NumberParseException $e) {
253
-				}
254
-
255
-				if ($defaultPhoneRegion !== '' && $defaultPhoneRegion !== $location && strpos($phone, '0') === 0) {
256
-					// If the number has a leading zero (no country code),
257
-					// we also check the default phone region of the instance,
258
-					// when it's different to the user's given region.
259
-					try {
260
-						$phoneNumber = $phoneUtil->parse($phone, $defaultPhoneRegion);
261
-						if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
262
-							$normalizedNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
263
-							$normalizedNumberToKey[$normalizedNumber] = (string) $key;
264
-						}
265
-					} catch (NumberParseException $e) {
266
-					}
267
-				}
268
-			}
269
-		}
270
-
271
-		$phoneNumbers = array_keys($normalizedNumberToKey);
272
-
273
-		if (empty($phoneNumbers)) {
274
-			return new DataResponse();
275
-		}
276
-
277
-		// Cleanup all previous entries and only allow new matches
278
-		$this->knownUserService->deleteKnownTo($knownTo);
279
-
280
-		$userMatches = $this->accountManager->searchUsers(IAccountManager::PROPERTY_PHONE, $phoneNumbers);
281
-
282
-		if (empty($userMatches)) {
283
-			return new DataResponse();
284
-		}
285
-
286
-		$cloudUrl = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
287
-		if (strpos($cloudUrl, 'http://') === 0) {
288
-			$cloudUrl = substr($cloudUrl, strlen('http://'));
289
-		} elseif (strpos($cloudUrl, 'https://') === 0) {
290
-			$cloudUrl = substr($cloudUrl, strlen('https://'));
291
-		}
292
-
293
-		$matches = [];
294
-		foreach ($userMatches as $phone => $userId) {
295
-			// Not using the ICloudIdManager as that would run a search for each contact to find the display name in the address book
296
-			$matches[$normalizedNumberToKey[$phone]] = $userId . '@' . $cloudUrl;
297
-			$this->knownUserService->storeIsKnownToUser($knownTo, $userId);
298
-		}
299
-
300
-		return new DataResponse($matches);
301
-	}
302
-
303
-	/**
304
-	 * @throws OCSException
305
-	 */
306
-	private function createNewUserId(): string {
307
-		$attempts = 0;
308
-		do {
309
-			$uidCandidate = $this->secureRandom->generate(10, ISecureRandom::CHAR_HUMAN_READABLE);
310
-			if (!$this->userManager->userExists($uidCandidate)) {
311
-				return $uidCandidate;
312
-			}
313
-			$attempts++;
314
-		} while ($attempts < 10);
315
-		throw new OCSException('Could not create non-existing user id', 111);
316
-	}
317
-
318
-	/**
319
-	 * @PasswordConfirmationRequired
320
-	 * @NoAdminRequired
321
-	 *
322
-	 * @param string $userid
323
-	 * @param string $password
324
-	 * @param string $displayName
325
-	 * @param string $email
326
-	 * @param array $groups
327
-	 * @param array $subadmin
328
-	 * @param string $quota
329
-	 * @param string $language
330
-	 * @return DataResponse
331
-	 * @throws OCSException
332
-	 */
333
-	public function addUser(
334
-		string $userid,
335
-		string $password = '',
336
-		string $displayName = '',
337
-		string $email = '',
338
-		array $groups = [],
339
-		array $subadmin = [],
340
-		string $quota = '',
341
-		string $language = ''
342
-	): DataResponse {
343
-		$user = $this->userSession->getUser();
344
-		$isAdmin = $this->groupManager->isAdmin($user->getUID());
345
-		$subAdminManager = $this->groupManager->getSubAdmin();
346
-
347
-		if (empty($userid) && $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes') {
348
-			$userid = $this->createNewUserId();
349
-		}
350
-
351
-		if ($this->userManager->userExists($userid)) {
352
-			$this->logger->error('Failed addUser attempt: User already exists.', ['app' => 'ocs_api']);
353
-			throw new OCSException($this->l10nFactory->get('provisioning_api')->t('User already exists'), 102);
354
-		}
355
-
356
-		if ($groups !== []) {
357
-			foreach ($groups as $group) {
358
-				if (!$this->groupManager->groupExists($group)) {
359
-					throw new OCSException('group ' . $group . ' does not exist', 104);
360
-				}
361
-				if (!$isAdmin && !$subAdminManager->isSubAdminOfGroup($user, $this->groupManager->get($group))) {
362
-					throw new OCSException('insufficient privileges for group ' . $group, 105);
363
-				}
364
-			}
365
-		} else {
366
-			if (!$isAdmin) {
367
-				throw new OCSException('no group specified (required for subadmins)', 106);
368
-			}
369
-		}
370
-
371
-		$subadminGroups = [];
372
-		if ($subadmin !== []) {
373
-			foreach ($subadmin as $groupid) {
374
-				$group = $this->groupManager->get($groupid);
375
-				// Check if group exists
376
-				if ($group === null) {
377
-					throw new OCSException('Subadmin group does not exist',  102);
378
-				}
379
-				// Check if trying to make subadmin of admin group
380
-				if ($group->getGID() === 'admin') {
381
-					throw new OCSException('Cannot create subadmins for admin group', 103);
382
-				}
383
-				// Check if has permission to promote subadmins
384
-				if (!$subAdminManager->isSubAdminOfGroup($user, $group) && !$isAdmin) {
385
-					throw new OCSForbiddenException('No permissions to promote subadmins');
386
-				}
387
-				$subadminGroups[] = $group;
388
-			}
389
-		}
390
-
391
-		$generatePasswordResetToken = false;
392
-		if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
393
-			throw new OCSException('Invalid password value', 101);
394
-		}
395
-		if ($password === '') {
396
-			if ($email === '') {
397
-				throw new OCSException('To send a password link to the user an email address is required.', 108);
398
-			}
399
-
400
-			$passwordEvent = new GenerateSecurePasswordEvent();
401
-			$this->eventDispatcher->dispatchTyped($passwordEvent);
402
-
403
-			$password = $passwordEvent->getPassword();
404
-			if ($password === null) {
405
-				// Fallback: ensure to pass password_policy in any case
406
-				$password = $this->secureRandom->generate(10)
407
-					. $this->secureRandom->generate(1, ISecureRandom::CHAR_UPPER)
408
-					. $this->secureRandom->generate(1, ISecureRandom::CHAR_LOWER)
409
-					. $this->secureRandom->generate(1, ISecureRandom::CHAR_DIGITS)
410
-					. $this->secureRandom->generate(1, ISecureRandom::CHAR_SYMBOLS);
411
-			}
412
-			$generatePasswordResetToken = true;
413
-		}
414
-
415
-		if ($email === '' && $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes') {
416
-			throw new OCSException('Required email address was not provided', 110);
417
-		}
418
-
419
-		try {
420
-			$newUser = $this->userManager->createUser($userid, $password);
421
-			$this->logger->info('Successful addUser call with userid: ' . $userid, ['app' => 'ocs_api']);
422
-
423
-			foreach ($groups as $group) {
424
-				$this->groupManager->get($group)->addUser($newUser);
425
-				$this->logger->info('Added userid ' . $userid . ' to group ' . $group, ['app' => 'ocs_api']);
426
-			}
427
-			foreach ($subadminGroups as $group) {
428
-				$subAdminManager->createSubAdmin($newUser, $group);
429
-			}
430
-
431
-			if ($displayName !== '') {
432
-				try {
433
-					$this->editUser($userid, self::USER_FIELD_DISPLAYNAME, $displayName);
434
-				} catch (OCSException $e) {
435
-					if ($newUser instanceof IUser) {
436
-						$newUser->delete();
437
-					}
438
-					throw $e;
439
-				}
440
-			}
441
-
442
-			if ($quota !== '') {
443
-				$this->editUser($userid, self::USER_FIELD_QUOTA, $quota);
444
-			}
445
-
446
-			if ($language !== '') {
447
-				$this->editUser($userid, self::USER_FIELD_LANGUAGE, $language);
448
-			}
449
-
450
-			// Send new user mail only if a mail is set
451
-			if ($email !== '') {
452
-				$newUser->setEMailAddress($email);
453
-				if ($this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes') {
454
-					try {
455
-						$emailTemplate = $this->newUserMailHelper->generateTemplate($newUser, $generatePasswordResetToken);
456
-						$this->newUserMailHelper->sendMail($newUser, $emailTemplate);
457
-					} catch (\Exception $e) {
458
-						// Mail could be failing hard or just be plain not configured
459
-						// Logging error as it is the hardest of the two
460
-						$this->logger->error(
461
-							"Unable to send the invitation mail to $email",
462
-							[
463
-								'app' => 'ocs_api',
464
-								'exception' => $e,
465
-							]
466
-						);
467
-					}
468
-				}
469
-			}
470
-
471
-			return new DataResponse(['id' => $userid]);
472
-		} catch (HintException $e) {
473
-			$this->logger->warning(
474
-				'Failed addUser attempt with hint exception.',
475
-				[
476
-					'app' => 'ocs_api',
477
-					'exception' => $e,
478
-				]
479
-			);
480
-			throw new OCSException($e->getHint(), 107);
481
-		} catch (OCSException $e) {
482
-			$this->logger->warning(
483
-				'Failed addUser attempt with ocs exception.',
484
-				[
485
-					'app' => 'ocs_api',
486
-					'exception' => $e,
487
-				]
488
-			);
489
-			throw $e;
490
-		} catch (InvalidArgumentException $e) {
491
-			$this->logger->error(
492
-				'Failed addUser attempt with invalid argument exception.',
493
-				[
494
-					'app' => 'ocs_api',
495
-					'exception' => $e,
496
-				]
497
-			);
498
-			throw new OCSException($e->getMessage(), 101);
499
-		} catch (\Exception $e) {
500
-			$this->logger->error(
501
-				'Failed addUser attempt with exception.',
502
-				[
503
-					'app' => 'ocs_api',
504
-					'exception' => $e
505
-				]
506
-			);
507
-			throw new OCSException('Bad request', 101);
508
-		}
509
-	}
510
-
511
-	/**
512
-	 * @NoAdminRequired
513
-	 * @NoSubAdminRequired
514
-	 *
515
-	 * gets user info
516
-	 *
517
-	 * @param string $userId
518
-	 * @return DataResponse
519
-	 * @throws OCSException
520
-	 */
521
-	public function getUser(string $userId): DataResponse {
522
-		$includeScopes = false;
523
-		$currentUser = $this->userSession->getUser();
524
-		if ($currentUser && $currentUser->getUID() === $userId) {
525
-			$includeScopes = true;
526
-		}
527
-
528
-		$data = $this->getUserData($userId, $includeScopes);
529
-		// getUserData returns empty array if not enough permissions
530
-		if (empty($data)) {
531
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
532
-		}
533
-		return new DataResponse($data);
534
-	}
535
-
536
-	/**
537
-	 * @NoAdminRequired
538
-	 * @NoSubAdminRequired
539
-	 *
540
-	 * gets user info from the currently logged in user
541
-	 *
542
-	 * @return DataResponse
543
-	 * @throws OCSException
544
-	 */
545
-	public function getCurrentUser(): DataResponse {
546
-		$user = $this->userSession->getUser();
547
-		if ($user) {
548
-			$data = $this->getUserData($user->getUID(), true);
549
-			// rename "displayname" to "display-name" only for this call to keep
550
-			// the API stable.
551
-			$data['display-name'] = $data['displayname'];
552
-			unset($data['displayname']);
553
-			return new DataResponse($data);
554
-		}
555
-
556
-		throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
557
-	}
558
-
559
-	/**
560
-	 * @NoAdminRequired
561
-	 * @NoSubAdminRequired
562
-	 *
563
-	 * @return DataResponse
564
-	 * @throws OCSException
565
-	 */
566
-	public function getEditableFields(): DataResponse {
567
-		$currentLoggedInUser = $this->userSession->getUser();
568
-		if (!$currentLoggedInUser instanceof IUser) {
569
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
570
-		}
571
-
572
-		return $this->getEditableFieldsForUser($currentLoggedInUser->getUID());
573
-	}
574
-
575
-	/**
576
-	 * @NoAdminRequired
577
-	 * @NoSubAdminRequired
578
-	 *
579
-	 * @param string $userId
580
-	 * @return DataResponse
581
-	 * @throws OCSException
582
-	 */
583
-	public function getEditableFieldsForUser(string $userId): DataResponse {
584
-		$currentLoggedInUser = $this->userSession->getUser();
585
-		if (!$currentLoggedInUser instanceof IUser) {
586
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
587
-		}
588
-
589
-		$permittedFields = [];
590
-
591
-		if ($userId !== $currentLoggedInUser->getUID()) {
592
-			$targetUser = $this->userManager->get($userId);
593
-			if (!$targetUser instanceof IUser) {
594
-				throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
595
-			}
596
-
597
-			$subAdminManager = $this->groupManager->getSubAdmin();
598
-			if (
599
-				!$this->groupManager->isAdmin($currentLoggedInUser->getUID())
600
-				&& !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
601
-			) {
602
-				throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
603
-			}
604
-		} else {
605
-			$targetUser = $currentLoggedInUser;
606
-		}
607
-
608
-		// Editing self (display, email)
609
-		if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
610
-			if (
611
-				$targetUser->getBackend() instanceof ISetDisplayNameBackend
612
-				|| $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
613
-			) {
614
-				$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
615
-			}
616
-			$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
617
-		}
618
-
619
-		$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
620
-		$permittedFields[] = IAccountManager::PROPERTY_PHONE;
621
-		$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
622
-		$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
623
-		$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
624
-		$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
625
-		$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
626
-		$permittedFields[] = IAccountManager::PROPERTY_ROLE;
627
-		$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
628
-		$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
629
-		$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
630
-
631
-		return new DataResponse($permittedFields);
632
-	}
633
-
634
-	/**
635
-	 * @NoAdminRequired
636
-	 * @NoSubAdminRequired
637
-	 * @PasswordConfirmationRequired
638
-	 *
639
-	 * @throws OCSException
640
-	 */
641
-	public function editUserMultiValue(
642
-		string $userId,
643
-		string $collectionName,
644
-		string $key,
645
-		string $value
646
-	): DataResponse {
647
-		$currentLoggedInUser = $this->userSession->getUser();
648
-		if ($currentLoggedInUser === null) {
649
-			throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
650
-		}
651
-
652
-		$targetUser = $this->userManager->get($userId);
653
-		if ($targetUser === null) {
654
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
655
-		}
656
-
657
-		$subAdminManager = $this->groupManager->getSubAdmin();
658
-		$isAdminOrSubadmin = $this->groupManager->isAdmin($currentLoggedInUser->getUID())
659
-			|| $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser);
660
-
661
-		$permittedFields = [];
662
-		if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
663
-			// Editing self (display, email)
664
-			$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
665
-			$permittedFields[] = IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX;
666
-		} else {
667
-			// Check if admin / subadmin
668
-			if ($isAdminOrSubadmin) {
669
-				// They have permissions over the user
670
-				$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
671
-			} else {
672
-				// No rights
673
-				throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
674
-			}
675
-		}
676
-
677
-		// Check if permitted to edit this field
678
-		if (!in_array($collectionName, $permittedFields)) {
679
-			throw new OCSException('', 103);
680
-		}
681
-
682
-		switch ($collectionName) {
683
-			case IAccountManager::COLLECTION_EMAIL:
684
-				$userAccount = $this->accountManager->getAccount($targetUser);
685
-				$mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
686
-				$mailCollection->removePropertyByValue($key);
687
-				if ($value !== '') {
688
-					$mailCollection->addPropertyWithDefaults($value);
689
-					$property = $mailCollection->getPropertyByValue($key);
690
-					if ($isAdminOrSubadmin && $property) {
691
-						// admin set mails are auto-verified
692
-						$property->setLocallyVerified(IAccountManager::VERIFIED);
693
-					}
694
-				}
695
-				$this->accountManager->updateAccount($userAccount);
696
-				break;
697
-
698
-			case IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX:
699
-				$userAccount = $this->accountManager->getAccount($targetUser);
700
-				$mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
701
-				$targetProperty = null;
702
-				foreach ($mailCollection->getProperties() as $property) {
703
-					if ($property->getValue() === $key) {
704
-						$targetProperty = $property;
705
-						break;
706
-					}
707
-				}
708
-				if ($targetProperty instanceof IAccountProperty) {
709
-					try {
710
-						$targetProperty->setScope($value);
711
-						$this->accountManager->updateAccount($userAccount);
712
-					} catch (InvalidArgumentException $e) {
713
-						throw new OCSException('', 102);
714
-					}
715
-				} else {
716
-					throw new OCSException('', 102);
717
-				}
718
-				break;
719
-
720
-			default:
721
-				throw new OCSException('', 103);
722
-		}
723
-		return new DataResponse();
724
-	}
725
-
726
-	/**
727
-	 * @NoAdminRequired
728
-	 * @NoSubAdminRequired
729
-	 * @PasswordConfirmationRequired
730
-	 *
731
-	 * edit users
732
-	 *
733
-	 * @param string $userId
734
-	 * @param string $key
735
-	 * @param string $value
736
-	 * @return DataResponse
737
-	 * @throws OCSException
738
-	 */
739
-	public function editUser(string $userId, string $key, string $value): DataResponse {
740
-		$currentLoggedInUser = $this->userSession->getUser();
741
-
742
-		$targetUser = $this->userManager->get($userId);
743
-		if ($targetUser === null) {
744
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
745
-		}
746
-
747
-		$permittedFields = [];
748
-		if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
749
-			// Editing self (display, email)
750
-			if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
751
-				if (
752
-					$targetUser->getBackend() instanceof ISetDisplayNameBackend
753
-					|| $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
754
-				) {
755
-					$permittedFields[] = self::USER_FIELD_DISPLAYNAME;
756
-					$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
757
-				}
758
-				$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
759
-			}
760
-
761
-			$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX;
762
-			$permittedFields[] = IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX;
763
-
764
-			$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
765
-
766
-			$permittedFields[] = self::USER_FIELD_PASSWORD;
767
-			$permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL;
768
-			if (
769
-				$this->config->getSystemValue('force_language', false) === false ||
770
-				$this->groupManager->isAdmin($currentLoggedInUser->getUID())
771
-			) {
772
-				$permittedFields[] = self::USER_FIELD_LANGUAGE;
773
-			}
774
-
775
-			if (
776
-				$this->config->getSystemValue('force_locale', false) === false ||
777
-				$this->groupManager->isAdmin($currentLoggedInUser->getUID())
778
-			) {
779
-				$permittedFields[] = self::USER_FIELD_LOCALE;
780
-			}
781
-
782
-			$permittedFields[] = IAccountManager::PROPERTY_PHONE;
783
-			$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
784
-			$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
785
-			$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
786
-			$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
787
-			$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
788
-			$permittedFields[] = IAccountManager::PROPERTY_ROLE;
789
-			$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
790
-			$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
791
-			$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
792
-			$permittedFields[] = IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX;
793
-			$permittedFields[] = IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX;
794
-			$permittedFields[] = IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX;
795
-			$permittedFields[] = IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX;
796
-			$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE . self::SCOPE_SUFFIX;
797
-			$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX;
798
-			$permittedFields[] = IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX;
799
-			$permittedFields[] = IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX;
800
-			$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX;
801
-			$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX;
802
-
803
-			$permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX;
804
-
805
-			// If admin they can edit their own quota
806
-			if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
807
-				$permittedFields[] = self::USER_FIELD_QUOTA;
808
-			}
809
-		} else {
810
-			// Check if admin / subadmin
811
-			$subAdminManager = $this->groupManager->getSubAdmin();
812
-			if (
813
-				$this->groupManager->isAdmin($currentLoggedInUser->getUID())
814
-				|| $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
815
-			) {
816
-				// They have permissions over the user
817
-				if (
818
-					$targetUser->getBackend() instanceof ISetDisplayNameBackend
819
-					|| $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
820
-				) {
821
-					$permittedFields[] = self::USER_FIELD_DISPLAYNAME;
822
-					$permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
823
-				}
824
-				$permittedFields[] = IAccountManager::PROPERTY_EMAIL;
825
-				$permittedFields[] = IAccountManager::COLLECTION_EMAIL;
826
-				$permittedFields[] = self::USER_FIELD_PASSWORD;
827
-				$permittedFields[] = self::USER_FIELD_LANGUAGE;
828
-				$permittedFields[] = self::USER_FIELD_LOCALE;
829
-				$permittedFields[] = IAccountManager::PROPERTY_PHONE;
830
-				$permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
831
-				$permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
832
-				$permittedFields[] = IAccountManager::PROPERTY_TWITTER;
833
-				$permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
834
-				$permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
835
-				$permittedFields[] = IAccountManager::PROPERTY_ROLE;
836
-				$permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
837
-				$permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
838
-				$permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
839
-				$permittedFields[] = self::USER_FIELD_QUOTA;
840
-				$permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL;
841
-			} else {
842
-				// No rights
843
-				throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
844
-			}
845
-		}
846
-		// Check if permitted to edit this field
847
-		if (!in_array($key, $permittedFields)) {
848
-			throw new OCSException('', 103);
849
-		}
850
-		// Process the edit
851
-		switch ($key) {
852
-			case self::USER_FIELD_DISPLAYNAME:
853
-			case IAccountManager::PROPERTY_DISPLAYNAME:
854
-				try {
855
-					$targetUser->setDisplayName($value);
856
-				} catch (InvalidArgumentException $e) {
857
-					throw new OCSException($e->getMessage(), 101);
858
-				}
859
-				break;
860
-			case self::USER_FIELD_QUOTA:
861
-				$quota = $value;
862
-				if ($quota !== 'none' && $quota !== 'default') {
863
-					if (is_numeric($quota)) {
864
-						$quota = (float) $quota;
865
-					} else {
866
-						$quota = \OCP\Util::computerFileSize($quota);
867
-					}
868
-					if ($quota === false) {
869
-						throw new OCSException('Invalid quota value ' . $value, 102);
870
-					}
871
-					if ($quota === -1) {
872
-						$quota = 'none';
873
-					} else {
874
-						$maxQuota = (int) $this->config->getAppValue('files', 'max_quota', '-1');
875
-						if ($maxQuota !== -1 && $quota > $maxQuota) {
876
-							throw new OCSException('Invalid quota value. ' . $value . ' is exceeding the maximum quota', 102);
877
-						}
878
-						$quota = \OCP\Util::humanFileSize($quota);
879
-					}
880
-				}
881
-				// no else block because quota can be set to 'none' in previous if
882
-				if ($quota === 'none') {
883
-					$allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
884
-					if (!$allowUnlimitedQuota) {
885
-						throw new OCSException('Unlimited quota is forbidden on this instance', 102);
886
-					}
887
-				}
888
-				$targetUser->setQuota($quota);
889
-				break;
890
-			case self::USER_FIELD_PASSWORD:
891
-				try {
892
-					if (strlen($value) > IUserManager::MAX_PASSWORD_LENGTH) {
893
-						throw new OCSException('Invalid password value', 102);
894
-					}
895
-					if (!$targetUser->canChangePassword()) {
896
-						throw new OCSException('Setting the password is not supported by the users backend', 103);
897
-					}
898
-					$targetUser->setPassword($value);
899
-				} catch (HintException $e) { // password policy error
900
-					throw new OCSException($e->getMessage(), 103);
901
-				}
902
-				break;
903
-			case self::USER_FIELD_LANGUAGE:
904
-				$languagesCodes = $this->l10nFactory->findAvailableLanguages();
905
-				if (!in_array($value, $languagesCodes, true) && $value !== 'en') {
906
-					throw new OCSException('Invalid language', 102);
907
-				}
908
-				$this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value);
909
-				break;
910
-			case self::USER_FIELD_LOCALE:
911
-				if (!$this->l10nFactory->localeExists($value)) {
912
-					throw new OCSException('Invalid locale', 102);
913
-				}
914
-				$this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value);
915
-				break;
916
-			case self::USER_FIELD_NOTIFICATION_EMAIL:
917
-				$success = false;
918
-				if ($value === '' || filter_var($value, FILTER_VALIDATE_EMAIL)) {
919
-					try {
920
-						$targetUser->setPrimaryEMailAddress($value);
921
-						$success = true;
922
-					} catch (InvalidArgumentException $e) {
923
-						$this->logger->info(
924
-							'Cannot set primary email, because provided address is not verified',
925
-							[
926
-								'app' => 'provisioning_api',
927
-								'exception' => $e,
928
-							]
929
-						);
930
-					}
931
-				}
932
-				if (!$success) {
933
-					throw new OCSException('', 102);
934
-				}
935
-				break;
936
-			case IAccountManager::PROPERTY_EMAIL:
937
-				if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') {
938
-					$targetUser->setEMailAddress($value);
939
-				} else {
940
-					throw new OCSException('', 102);
941
-				}
942
-				break;
943
-			case IAccountManager::COLLECTION_EMAIL:
944
-				if (filter_var($value, FILTER_VALIDATE_EMAIL) && $value !== $targetUser->getSystemEMailAddress()) {
945
-					$userAccount = $this->accountManager->getAccount($targetUser);
946
-					$mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
947
-					foreach ($mailCollection->getProperties() as $property) {
948
-						if ($property->getValue() === $value) {
949
-							break;
950
-						}
951
-					}
952
-					$mailCollection->addPropertyWithDefaults($value);
953
-					$this->accountManager->updateAccount($userAccount);
954
-				} else {
955
-					throw new OCSException('', 102);
956
-				}
957
-				break;
958
-			case IAccountManager::PROPERTY_PHONE:
959
-			case IAccountManager::PROPERTY_ADDRESS:
960
-			case IAccountManager::PROPERTY_WEBSITE:
961
-			case IAccountManager::PROPERTY_TWITTER:
962
-			case IAccountManager::PROPERTY_FEDIVERSE:
963
-			case IAccountManager::PROPERTY_ORGANISATION:
964
-			case IAccountManager::PROPERTY_ROLE:
965
-			case IAccountManager::PROPERTY_HEADLINE:
966
-			case IAccountManager::PROPERTY_BIOGRAPHY:
967
-				$userAccount = $this->accountManager->getAccount($targetUser);
968
-				try {
969
-					$userProperty = $userAccount->getProperty($key);
970
-					if ($userProperty->getValue() !== $value) {
971
-						try {
972
-							$userProperty->setValue($value);
973
-							if ($userProperty->getName() === IAccountManager::PROPERTY_PHONE) {
974
-								$this->knownUserService->deleteByContactUserId($targetUser->getUID());
975
-							}
976
-						} catch (InvalidArgumentException $e) {
977
-							throw new OCSException('Invalid ' . $e->getMessage(), 102);
978
-						}
979
-					}
980
-				} catch (PropertyDoesNotExistException $e) {
981
-					$userAccount->setProperty($key, $value, IAccountManager::SCOPE_PRIVATE, IAccountManager::NOT_VERIFIED);
982
-				}
983
-				try {
984
-					$this->accountManager->updateAccount($userAccount);
985
-				} catch (InvalidArgumentException $e) {
986
-					throw new OCSException('Invalid ' . $e->getMessage(), 102);
987
-				}
988
-				break;
989
-			case IAccountManager::PROPERTY_PROFILE_ENABLED:
990
-				$userAccount = $this->accountManager->getAccount($targetUser);
991
-				try {
992
-					$userProperty = $userAccount->getProperty($key);
993
-					if ($userProperty->getValue() !== $value) {
994
-						$userProperty->setValue($value);
995
-					}
996
-				} catch (PropertyDoesNotExistException $e) {
997
-					$userAccount->setProperty($key, $value, IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
998
-				}
999
-				$this->accountManager->updateAccount($userAccount);
1000
-				break;
1001
-			case IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX:
1002
-			case IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX:
1003
-			case IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX:
1004
-			case IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX:
1005
-			case IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX:
1006
-			case IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX:
1007
-			case IAccountManager::PROPERTY_FEDIVERSE . self::SCOPE_SUFFIX:
1008
-			case IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX:
1009
-			case IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX:
1010
-			case IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX:
1011
-			case IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX:
1012
-			case IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX:
1013
-			case IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX:
1014
-				$propertyName = substr($key, 0, strlen($key) - strlen(self::SCOPE_SUFFIX));
1015
-				$userAccount = $this->accountManager->getAccount($targetUser);
1016
-				$userProperty = $userAccount->getProperty($propertyName);
1017
-				if ($userProperty->getScope() !== $value) {
1018
-					try {
1019
-						$userProperty->setScope($value);
1020
-						$this->accountManager->updateAccount($userAccount);
1021
-					} catch (InvalidArgumentException $e) {
1022
-						throw new OCSException('Invalid ' . $e->getMessage(), 102);
1023
-					}
1024
-				}
1025
-				break;
1026
-			default:
1027
-				throw new OCSException('', 103);
1028
-		}
1029
-		return new DataResponse();
1030
-	}
1031
-
1032
-	/**
1033
-	 * @PasswordConfirmationRequired
1034
-	 * @NoAdminRequired
1035
-	 *
1036
-	 * @param string $userId
1037
-	 *
1038
-	 * @return DataResponse
1039
-	 *
1040
-	 * @throws OCSException
1041
-	 */
1042
-	public function wipeUserDevices(string $userId): DataResponse {
1043
-		/** @var IUser $currentLoggedInUser */
1044
-		$currentLoggedInUser = $this->userSession->getUser();
1045
-
1046
-		$targetUser = $this->userManager->get($userId);
1047
-
1048
-		if ($targetUser === null) {
1049
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1050
-		}
1051
-
1052
-		if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
1053
-			throw new OCSException('', 101);
1054
-		}
1055
-
1056
-		// If not permitted
1057
-		$subAdminManager = $this->groupManager->getSubAdmin();
1058
-		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
1059
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1060
-		}
1061
-
1062
-		$this->remoteWipe->markAllTokensForWipe($targetUser);
1063
-
1064
-		return new DataResponse();
1065
-	}
1066
-
1067
-	/**
1068
-	 * @PasswordConfirmationRequired
1069
-	 * @NoAdminRequired
1070
-	 *
1071
-	 * @param string $userId
1072
-	 * @return DataResponse
1073
-	 * @throws OCSException
1074
-	 */
1075
-	public function deleteUser(string $userId): DataResponse {
1076
-		$currentLoggedInUser = $this->userSession->getUser();
1077
-
1078
-		$targetUser = $this->userManager->get($userId);
1079
-
1080
-		if ($targetUser === null) {
1081
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1082
-		}
1083
-
1084
-		if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
1085
-			throw new OCSException('', 101);
1086
-		}
1087
-
1088
-		// If not permitted
1089
-		$subAdminManager = $this->groupManager->getSubAdmin();
1090
-		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
1091
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1092
-		}
1093
-
1094
-		// Go ahead with the delete
1095
-		if ($targetUser->delete()) {
1096
-			return new DataResponse();
1097
-		} else {
1098
-			throw new OCSException('', 101);
1099
-		}
1100
-	}
1101
-
1102
-	/**
1103
-	 * @PasswordConfirmationRequired
1104
-	 * @NoAdminRequired
1105
-	 *
1106
-	 * @param string $userId
1107
-	 * @return DataResponse
1108
-	 * @throws OCSException
1109
-	 * @throws OCSForbiddenException
1110
-	 */
1111
-	public function disableUser(string $userId): DataResponse {
1112
-		return $this->setEnabled($userId, false);
1113
-	}
1114
-
1115
-	/**
1116
-	 * @PasswordConfirmationRequired
1117
-	 * @NoAdminRequired
1118
-	 *
1119
-	 * @param string $userId
1120
-	 * @return DataResponse
1121
-	 * @throws OCSException
1122
-	 * @throws OCSForbiddenException
1123
-	 */
1124
-	public function enableUser(string $userId): DataResponse {
1125
-		return $this->setEnabled($userId, true);
1126
-	}
1127
-
1128
-	/**
1129
-	 * @param string $userId
1130
-	 * @param bool $value
1131
-	 * @return DataResponse
1132
-	 * @throws OCSException
1133
-	 */
1134
-	private function setEnabled(string $userId, bool $value): DataResponse {
1135
-		$currentLoggedInUser = $this->userSession->getUser();
1136
-
1137
-		$targetUser = $this->userManager->get($userId);
1138
-		if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
1139
-			throw new OCSException('', 101);
1140
-		}
1141
-
1142
-		// If not permitted
1143
-		$subAdminManager = $this->groupManager->getSubAdmin();
1144
-		if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
1145
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1146
-		}
1147
-
1148
-		// enable/disable the user now
1149
-		$targetUser->setEnabled($value);
1150
-		return new DataResponse();
1151
-	}
1152
-
1153
-	/**
1154
-	 * @NoAdminRequired
1155
-	 * @NoSubAdminRequired
1156
-	 *
1157
-	 * @param string $userId
1158
-	 * @return DataResponse
1159
-	 * @throws OCSException
1160
-	 */
1161
-	public function getUsersGroups(string $userId): DataResponse {
1162
-		$loggedInUser = $this->userSession->getUser();
1163
-
1164
-		$targetUser = $this->userManager->get($userId);
1165
-		if ($targetUser === null) {
1166
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1167
-		}
1168
-
1169
-		if ($targetUser->getUID() === $loggedInUser->getUID() || $this->groupManager->isAdmin($loggedInUser->getUID())) {
1170
-			// Self lookup or admin lookup
1171
-			return new DataResponse([
1172
-				'groups' => $this->groupManager->getUserGroupIds($targetUser)
1173
-			]);
1174
-		} else {
1175
-			$subAdminManager = $this->groupManager->getSubAdmin();
1176
-
1177
-			// Looking up someone else
1178
-			if ($subAdminManager->isUserAccessible($loggedInUser, $targetUser)) {
1179
-				// Return the group that the method caller is subadmin of for the user in question
1180
-				/** @var IGroup[] $getSubAdminsGroups */
1181
-				$getSubAdminsGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
1182
-				foreach ($getSubAdminsGroups as $key => $group) {
1183
-					$getSubAdminsGroups[$key] = $group->getGID();
1184
-				}
1185
-				$groups = array_intersect(
1186
-					$getSubAdminsGroups,
1187
-					$this->groupManager->getUserGroupIds($targetUser)
1188
-				);
1189
-				return new DataResponse(['groups' => $groups]);
1190
-			} else {
1191
-				// Not permitted
1192
-				throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1193
-			}
1194
-		}
1195
-	}
1196
-
1197
-	/**
1198
-	 * @PasswordConfirmationRequired
1199
-	 * @NoAdminRequired
1200
-	 *
1201
-	 * @param string $userId
1202
-	 * @param string $groupid
1203
-	 * @return DataResponse
1204
-	 * @throws OCSException
1205
-	 */
1206
-	public function addToGroup(string $userId, string $groupid = ''): DataResponse {
1207
-		if ($groupid === '') {
1208
-			throw new OCSException('', 101);
1209
-		}
1210
-
1211
-		$group = $this->groupManager->get($groupid);
1212
-		$targetUser = $this->userManager->get($userId);
1213
-		if ($group === null) {
1214
-			throw new OCSException('', 102);
1215
-		}
1216
-		if ($targetUser === null) {
1217
-			throw new OCSException('', 103);
1218
-		}
1219
-
1220
-		// If they're not an admin, check they are a subadmin of the group in question
1221
-		$loggedInUser = $this->userSession->getUser();
1222
-		$subAdminManager = $this->groupManager->getSubAdmin();
1223
-		if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
1224
-			throw new OCSException('', 104);
1225
-		}
1226
-
1227
-		// Add user to group
1228
-		$group->addUser($targetUser);
1229
-		return new DataResponse();
1230
-	}
1231
-
1232
-	/**
1233
-	 * @PasswordConfirmationRequired
1234
-	 * @NoAdminRequired
1235
-	 *
1236
-	 * @param string $userId
1237
-	 * @param string $groupid
1238
-	 * @return DataResponse
1239
-	 * @throws OCSException
1240
-	 */
1241
-	public function removeFromGroup(string $userId, string $groupid): DataResponse {
1242
-		$loggedInUser = $this->userSession->getUser();
1243
-
1244
-		if ($groupid === null || trim($groupid) === '') {
1245
-			throw new OCSException('', 101);
1246
-		}
1247
-
1248
-		$group = $this->groupManager->get($groupid);
1249
-		if ($group === null) {
1250
-			throw new OCSException('', 102);
1251
-		}
1252
-
1253
-		$targetUser = $this->userManager->get($userId);
1254
-		if ($targetUser === null) {
1255
-			throw new OCSException('', 103);
1256
-		}
1257
-
1258
-		// If they're not an admin, check they are a subadmin of the group in question
1259
-		$subAdminManager = $this->groupManager->getSubAdmin();
1260
-		if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
1261
-			throw new OCSException('', 104);
1262
-		}
1263
-
1264
-		// Check they aren't removing themselves from 'admin' or their 'subadmin; group
1265
-		if ($targetUser->getUID() === $loggedInUser->getUID()) {
1266
-			if ($this->groupManager->isAdmin($loggedInUser->getUID())) {
1267
-				if ($group->getGID() === 'admin') {
1268
-					throw new OCSException('Cannot remove yourself from the admin group', 105);
1269
-				}
1270
-			} else {
1271
-				// Not an admin, so the user must be a subadmin of this group, but that is not allowed.
1272
-				throw new OCSException('Cannot remove yourself from this group as you are a SubAdmin', 105);
1273
-			}
1274
-		} elseif (!$this->groupManager->isAdmin($loggedInUser->getUID())) {
1275
-			/** @var IGroup[] $subAdminGroups */
1276
-			$subAdminGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
1277
-			$subAdminGroups = array_map(function (IGroup $subAdminGroup) {
1278
-				return $subAdminGroup->getGID();
1279
-			}, $subAdminGroups);
1280
-			$userGroups = $this->groupManager->getUserGroupIds($targetUser);
1281
-			$userSubAdminGroups = array_intersect($subAdminGroups, $userGroups);
1282
-
1283
-			if (count($userSubAdminGroups) <= 1) {
1284
-				// Subadmin must not be able to remove a user from all their subadmin groups.
1285
-				throw new OCSException('Not viable to remove user from the last group you are SubAdmin of', 105);
1286
-			}
1287
-		}
1288
-
1289
-		// Remove user from group
1290
-		$group->removeUser($targetUser);
1291
-		return new DataResponse();
1292
-	}
1293
-
1294
-	/**
1295
-	 * Creates a subadmin
1296
-	 *
1297
-	 * @PasswordConfirmationRequired
1298
-	 *
1299
-	 * @param string $userId
1300
-	 * @param string $groupid
1301
-	 * @return DataResponse
1302
-	 * @throws OCSException
1303
-	 */
1304
-	public function addSubAdmin(string $userId, string $groupid): DataResponse {
1305
-		$group = $this->groupManager->get($groupid);
1306
-		$user = $this->userManager->get($userId);
1307
-
1308
-		// Check if the user exists
1309
-		if ($user === null) {
1310
-			throw new OCSException('User does not exist', 101);
1311
-		}
1312
-		// Check if group exists
1313
-		if ($group === null) {
1314
-			throw new OCSException('Group does not exist',  102);
1315
-		}
1316
-		// Check if trying to make subadmin of admin group
1317
-		if ($group->getGID() === 'admin') {
1318
-			throw new OCSException('Cannot create subadmins for admin group', 103);
1319
-		}
1320
-
1321
-		$subAdminManager = $this->groupManager->getSubAdmin();
1322
-
1323
-		// We cannot be subadmin twice
1324
-		if ($subAdminManager->isSubAdminOfGroup($user, $group)) {
1325
-			return new DataResponse();
1326
-		}
1327
-		// Go
1328
-		$subAdminManager->createSubAdmin($user, $group);
1329
-		return new DataResponse();
1330
-	}
1331
-
1332
-	/**
1333
-	 * Removes a subadmin from a group
1334
-	 *
1335
-	 * @PasswordConfirmationRequired
1336
-	 *
1337
-	 * @param string $userId
1338
-	 * @param string $groupid
1339
-	 * @return DataResponse
1340
-	 * @throws OCSException
1341
-	 */
1342
-	public function removeSubAdmin(string $userId, string $groupid): DataResponse {
1343
-		$group = $this->groupManager->get($groupid);
1344
-		$user = $this->userManager->get($userId);
1345
-		$subAdminManager = $this->groupManager->getSubAdmin();
1346
-
1347
-		// Check if the user exists
1348
-		if ($user === null) {
1349
-			throw new OCSException('User does not exist', 101);
1350
-		}
1351
-		// Check if the group exists
1352
-		if ($group === null) {
1353
-			throw new OCSException('Group does not exist', 101);
1354
-		}
1355
-		// Check if they are a subadmin of this said group
1356
-		if (!$subAdminManager->isSubAdminOfGroup($user, $group)) {
1357
-			throw new OCSException('User is not a subadmin of this group', 102);
1358
-		}
1359
-
1360
-		// Go
1361
-		$subAdminManager->deleteSubAdmin($user, $group);
1362
-		return new DataResponse();
1363
-	}
1364
-
1365
-	/**
1366
-	 * Get the groups a user is a subadmin of
1367
-	 *
1368
-	 * @param string $userId
1369
-	 * @return DataResponse
1370
-	 * @throws OCSException
1371
-	 */
1372
-	public function getUserSubAdminGroups(string $userId): DataResponse {
1373
-		$groups = $this->getUserSubAdminGroupsData($userId);
1374
-		return new DataResponse($groups);
1375
-	}
1376
-
1377
-	/**
1378
-	 * @NoAdminRequired
1379
-	 * @PasswordConfirmationRequired
1380
-	 *
1381
-	 * resend welcome message
1382
-	 *
1383
-	 * @param string $userId
1384
-	 * @return DataResponse
1385
-	 * @throws OCSException
1386
-	 */
1387
-	public function resendWelcomeMessage(string $userId): DataResponse {
1388
-		$currentLoggedInUser = $this->userSession->getUser();
1389
-
1390
-		$targetUser = $this->userManager->get($userId);
1391
-		if ($targetUser === null) {
1392
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1393
-		}
1394
-
1395
-		// Check if admin / subadmin
1396
-		$subAdminManager = $this->groupManager->getSubAdmin();
1397
-		if (
1398
-			!$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
1399
-			&& !$this->groupManager->isAdmin($currentLoggedInUser->getUID())
1400
-		) {
1401
-			// No rights
1402
-			throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1403
-		}
1404
-
1405
-		$email = $targetUser->getEMailAddress();
1406
-		if ($email === '' || $email === null) {
1407
-			throw new OCSException('Email address not available', 101);
1408
-		}
1409
-
1410
-		try {
1411
-			$emailTemplate = $this->newUserMailHelper->generateTemplate($targetUser, false);
1412
-			$this->newUserMailHelper->sendMail($targetUser, $emailTemplate);
1413
-		} catch (\Exception $e) {
1414
-			$this->logger->error(
1415
-				"Can't send new user mail to $email",
1416
-				[
1417
-					'app' => 'settings',
1418
-					'exception' => $e,
1419
-				]
1420
-			);
1421
-			throw new OCSException('Sending email failed', 102);
1422
-		}
1423
-
1424
-		return new DataResponse();
1425
-	}
81
+    /** @var IURLGenerator */
82
+    protected $urlGenerator;
83
+    /** @var LoggerInterface */
84
+    private $logger;
85
+    /** @var IFactory */
86
+    protected $l10nFactory;
87
+    /** @var NewUserMailHelper */
88
+    private $newUserMailHelper;
89
+    /** @var ISecureRandom */
90
+    private $secureRandom;
91
+    /** @var RemoteWipe */
92
+    private $remoteWipe;
93
+    /** @var KnownUserService */
94
+    private $knownUserService;
95
+    /** @var IEventDispatcher */
96
+    private $eventDispatcher;
97
+
98
+    public function __construct(
99
+        string $appName,
100
+        IRequest $request,
101
+        IUserManager $userManager,
102
+        IConfig $config,
103
+        IGroupManager $groupManager,
104
+        IUserSession $userSession,
105
+        IAccountManager $accountManager,
106
+        IURLGenerator $urlGenerator,
107
+        LoggerInterface $logger,
108
+        IFactory $l10nFactory,
109
+        NewUserMailHelper $newUserMailHelper,
110
+        ISecureRandom $secureRandom,
111
+        RemoteWipe $remoteWipe,
112
+        KnownUserService $knownUserService,
113
+        IEventDispatcher $eventDispatcher
114
+    ) {
115
+        parent::__construct(
116
+            $appName,
117
+            $request,
118
+            $userManager,
119
+            $config,
120
+            $groupManager,
121
+            $userSession,
122
+            $accountManager,
123
+            $l10nFactory
124
+        );
125
+
126
+        $this->urlGenerator = $urlGenerator;
127
+        $this->logger = $logger;
128
+        $this->l10nFactory = $l10nFactory;
129
+        $this->newUserMailHelper = $newUserMailHelper;
130
+        $this->secureRandom = $secureRandom;
131
+        $this->remoteWipe = $remoteWipe;
132
+        $this->knownUserService = $knownUserService;
133
+        $this->eventDispatcher = $eventDispatcher;
134
+    }
135
+
136
+    /**
137
+     * @NoAdminRequired
138
+     *
139
+     * returns a list of users
140
+     *
141
+     * @param string $search
142
+     * @param int $limit
143
+     * @param int $offset
144
+     * @return DataResponse
145
+     */
146
+    public function getUsers(string $search = '', int $limit = null, int $offset = 0): DataResponse {
147
+        $user = $this->userSession->getUser();
148
+        $users = [];
149
+
150
+        // Admin? Or SubAdmin?
151
+        $uid = $user->getUID();
152
+        $subAdminManager = $this->groupManager->getSubAdmin();
153
+        if ($this->groupManager->isAdmin($uid)) {
154
+            $users = $this->userManager->search($search, $limit, $offset);
155
+        } elseif ($subAdminManager->isSubAdmin($user)) {
156
+            $subAdminOfGroups = $subAdminManager->getSubAdminsGroups($user);
157
+            foreach ($subAdminOfGroups as $key => $group) {
158
+                $subAdminOfGroups[$key] = $group->getGID();
159
+            }
160
+
161
+            $users = [];
162
+            foreach ($subAdminOfGroups as $group) {
163
+                $users = array_merge($users, $this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
164
+            }
165
+        }
166
+
167
+        $users = array_keys($users);
168
+
169
+        return new DataResponse([
170
+            'users' => $users
171
+        ]);
172
+    }
173
+
174
+    /**
175
+     * @NoAdminRequired
176
+     *
177
+     * returns a list of users and their data
178
+     */
179
+    public function getUsersDetails(string $search = '', int $limit = null, int $offset = 0): DataResponse {
180
+        $currentUser = $this->userSession->getUser();
181
+        $users = [];
182
+
183
+        // Admin? Or SubAdmin?
184
+        $uid = $currentUser->getUID();
185
+        $subAdminManager = $this->groupManager->getSubAdmin();
186
+        if ($this->groupManager->isAdmin($uid)) {
187
+            $users = $this->userManager->search($search, $limit, $offset);
188
+            $users = array_keys($users);
189
+        } elseif ($subAdminManager->isSubAdmin($currentUser)) {
190
+            $subAdminOfGroups = $subAdminManager->getSubAdminsGroups($currentUser);
191
+            foreach ($subAdminOfGroups as $key => $group) {
192
+                $subAdminOfGroups[$key] = $group->getGID();
193
+            }
194
+
195
+            $users = [];
196
+            foreach ($subAdminOfGroups as $group) {
197
+                $users[] = array_keys($this->groupManager->displayNamesInGroup($group, $search, $limit, $offset));
198
+            }
199
+            $users = array_merge(...$users);
200
+        }
201
+
202
+        $usersDetails = [];
203
+        foreach ($users as $userId) {
204
+            $userId = (string) $userId;
205
+            $userData = $this->getUserData($userId);
206
+            // Do not insert empty entry
207
+            if (!empty($userData)) {
208
+                $usersDetails[$userId] = $userData;
209
+            } else {
210
+                // Logged user does not have permissions to see this user
211
+                // only showing its id
212
+                $usersDetails[$userId] = ['id' => $userId];
213
+            }
214
+        }
215
+
216
+        return new DataResponse([
217
+            'users' => $usersDetails
218
+        ]);
219
+    }
220
+
221
+
222
+    /**
223
+     * @NoAdminRequired
224
+     * @NoSubAdminRequired
225
+     *
226
+     * @param string $location
227
+     * @param array $search
228
+     * @return DataResponse
229
+     */
230
+    public function searchByPhoneNumbers(string $location, array $search): DataResponse {
231
+        $phoneUtil = PhoneNumberUtil::getInstance();
232
+
233
+        if ($phoneUtil->getCountryCodeForRegion($location) === 0) {
234
+            // Not a valid region code
235
+            return new DataResponse([], Http::STATUS_BAD_REQUEST);
236
+        }
237
+
238
+        /** @var IUser $user */
239
+        $user = $this->userSession->getUser();
240
+        $knownTo = $user->getUID();
241
+        $defaultPhoneRegion = $this->config->getSystemValueString('default_phone_region');
242
+
243
+        $normalizedNumberToKey = [];
244
+        foreach ($search as $key => $phoneNumbers) {
245
+            foreach ($phoneNumbers as $phone) {
246
+                try {
247
+                    $phoneNumber = $phoneUtil->parse($phone, $location);
248
+                    if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
249
+                        $normalizedNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
250
+                        $normalizedNumberToKey[$normalizedNumber] = (string) $key;
251
+                    }
252
+                } catch (NumberParseException $e) {
253
+                }
254
+
255
+                if ($defaultPhoneRegion !== '' && $defaultPhoneRegion !== $location && strpos($phone, '0') === 0) {
256
+                    // If the number has a leading zero (no country code),
257
+                    // we also check the default phone region of the instance,
258
+                    // when it's different to the user's given region.
259
+                    try {
260
+                        $phoneNumber = $phoneUtil->parse($phone, $defaultPhoneRegion);
261
+                        if ($phoneNumber instanceof PhoneNumber && $phoneUtil->isValidNumber($phoneNumber)) {
262
+                            $normalizedNumber = $phoneUtil->format($phoneNumber, PhoneNumberFormat::E164);
263
+                            $normalizedNumberToKey[$normalizedNumber] = (string) $key;
264
+                        }
265
+                    } catch (NumberParseException $e) {
266
+                    }
267
+                }
268
+            }
269
+        }
270
+
271
+        $phoneNumbers = array_keys($normalizedNumberToKey);
272
+
273
+        if (empty($phoneNumbers)) {
274
+            return new DataResponse();
275
+        }
276
+
277
+        // Cleanup all previous entries and only allow new matches
278
+        $this->knownUserService->deleteKnownTo($knownTo);
279
+
280
+        $userMatches = $this->accountManager->searchUsers(IAccountManager::PROPERTY_PHONE, $phoneNumbers);
281
+
282
+        if (empty($userMatches)) {
283
+            return new DataResponse();
284
+        }
285
+
286
+        $cloudUrl = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/');
287
+        if (strpos($cloudUrl, 'http://') === 0) {
288
+            $cloudUrl = substr($cloudUrl, strlen('http://'));
289
+        } elseif (strpos($cloudUrl, 'https://') === 0) {
290
+            $cloudUrl = substr($cloudUrl, strlen('https://'));
291
+        }
292
+
293
+        $matches = [];
294
+        foreach ($userMatches as $phone => $userId) {
295
+            // Not using the ICloudIdManager as that would run a search for each contact to find the display name in the address book
296
+            $matches[$normalizedNumberToKey[$phone]] = $userId . '@' . $cloudUrl;
297
+            $this->knownUserService->storeIsKnownToUser($knownTo, $userId);
298
+        }
299
+
300
+        return new DataResponse($matches);
301
+    }
302
+
303
+    /**
304
+     * @throws OCSException
305
+     */
306
+    private function createNewUserId(): string {
307
+        $attempts = 0;
308
+        do {
309
+            $uidCandidate = $this->secureRandom->generate(10, ISecureRandom::CHAR_HUMAN_READABLE);
310
+            if (!$this->userManager->userExists($uidCandidate)) {
311
+                return $uidCandidate;
312
+            }
313
+            $attempts++;
314
+        } while ($attempts < 10);
315
+        throw new OCSException('Could not create non-existing user id', 111);
316
+    }
317
+
318
+    /**
319
+     * @PasswordConfirmationRequired
320
+     * @NoAdminRequired
321
+     *
322
+     * @param string $userid
323
+     * @param string $password
324
+     * @param string $displayName
325
+     * @param string $email
326
+     * @param array $groups
327
+     * @param array $subadmin
328
+     * @param string $quota
329
+     * @param string $language
330
+     * @return DataResponse
331
+     * @throws OCSException
332
+     */
333
+    public function addUser(
334
+        string $userid,
335
+        string $password = '',
336
+        string $displayName = '',
337
+        string $email = '',
338
+        array $groups = [],
339
+        array $subadmin = [],
340
+        string $quota = '',
341
+        string $language = ''
342
+    ): DataResponse {
343
+        $user = $this->userSession->getUser();
344
+        $isAdmin = $this->groupManager->isAdmin($user->getUID());
345
+        $subAdminManager = $this->groupManager->getSubAdmin();
346
+
347
+        if (empty($userid) && $this->config->getAppValue('core', 'newUser.generateUserID', 'no') === 'yes') {
348
+            $userid = $this->createNewUserId();
349
+        }
350
+
351
+        if ($this->userManager->userExists($userid)) {
352
+            $this->logger->error('Failed addUser attempt: User already exists.', ['app' => 'ocs_api']);
353
+            throw new OCSException($this->l10nFactory->get('provisioning_api')->t('User already exists'), 102);
354
+        }
355
+
356
+        if ($groups !== []) {
357
+            foreach ($groups as $group) {
358
+                if (!$this->groupManager->groupExists($group)) {
359
+                    throw new OCSException('group ' . $group . ' does not exist', 104);
360
+                }
361
+                if (!$isAdmin && !$subAdminManager->isSubAdminOfGroup($user, $this->groupManager->get($group))) {
362
+                    throw new OCSException('insufficient privileges for group ' . $group, 105);
363
+                }
364
+            }
365
+        } else {
366
+            if (!$isAdmin) {
367
+                throw new OCSException('no group specified (required for subadmins)', 106);
368
+            }
369
+        }
370
+
371
+        $subadminGroups = [];
372
+        if ($subadmin !== []) {
373
+            foreach ($subadmin as $groupid) {
374
+                $group = $this->groupManager->get($groupid);
375
+                // Check if group exists
376
+                if ($group === null) {
377
+                    throw new OCSException('Subadmin group does not exist',  102);
378
+                }
379
+                // Check if trying to make subadmin of admin group
380
+                if ($group->getGID() === 'admin') {
381
+                    throw new OCSException('Cannot create subadmins for admin group', 103);
382
+                }
383
+                // Check if has permission to promote subadmins
384
+                if (!$subAdminManager->isSubAdminOfGroup($user, $group) && !$isAdmin) {
385
+                    throw new OCSForbiddenException('No permissions to promote subadmins');
386
+                }
387
+                $subadminGroups[] = $group;
388
+            }
389
+        }
390
+
391
+        $generatePasswordResetToken = false;
392
+        if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
393
+            throw new OCSException('Invalid password value', 101);
394
+        }
395
+        if ($password === '') {
396
+            if ($email === '') {
397
+                throw new OCSException('To send a password link to the user an email address is required.', 108);
398
+            }
399
+
400
+            $passwordEvent = new GenerateSecurePasswordEvent();
401
+            $this->eventDispatcher->dispatchTyped($passwordEvent);
402
+
403
+            $password = $passwordEvent->getPassword();
404
+            if ($password === null) {
405
+                // Fallback: ensure to pass password_policy in any case
406
+                $password = $this->secureRandom->generate(10)
407
+                    . $this->secureRandom->generate(1, ISecureRandom::CHAR_UPPER)
408
+                    . $this->secureRandom->generate(1, ISecureRandom::CHAR_LOWER)
409
+                    . $this->secureRandom->generate(1, ISecureRandom::CHAR_DIGITS)
410
+                    . $this->secureRandom->generate(1, ISecureRandom::CHAR_SYMBOLS);
411
+            }
412
+            $generatePasswordResetToken = true;
413
+        }
414
+
415
+        if ($email === '' && $this->config->getAppValue('core', 'newUser.requireEmail', 'no') === 'yes') {
416
+            throw new OCSException('Required email address was not provided', 110);
417
+        }
418
+
419
+        try {
420
+            $newUser = $this->userManager->createUser($userid, $password);
421
+            $this->logger->info('Successful addUser call with userid: ' . $userid, ['app' => 'ocs_api']);
422
+
423
+            foreach ($groups as $group) {
424
+                $this->groupManager->get($group)->addUser($newUser);
425
+                $this->logger->info('Added userid ' . $userid . ' to group ' . $group, ['app' => 'ocs_api']);
426
+            }
427
+            foreach ($subadminGroups as $group) {
428
+                $subAdminManager->createSubAdmin($newUser, $group);
429
+            }
430
+
431
+            if ($displayName !== '') {
432
+                try {
433
+                    $this->editUser($userid, self::USER_FIELD_DISPLAYNAME, $displayName);
434
+                } catch (OCSException $e) {
435
+                    if ($newUser instanceof IUser) {
436
+                        $newUser->delete();
437
+                    }
438
+                    throw $e;
439
+                }
440
+            }
441
+
442
+            if ($quota !== '') {
443
+                $this->editUser($userid, self::USER_FIELD_QUOTA, $quota);
444
+            }
445
+
446
+            if ($language !== '') {
447
+                $this->editUser($userid, self::USER_FIELD_LANGUAGE, $language);
448
+            }
449
+
450
+            // Send new user mail only if a mail is set
451
+            if ($email !== '') {
452
+                $newUser->setEMailAddress($email);
453
+                if ($this->config->getAppValue('core', 'newUser.sendEmail', 'yes') === 'yes') {
454
+                    try {
455
+                        $emailTemplate = $this->newUserMailHelper->generateTemplate($newUser, $generatePasswordResetToken);
456
+                        $this->newUserMailHelper->sendMail($newUser, $emailTemplate);
457
+                    } catch (\Exception $e) {
458
+                        // Mail could be failing hard or just be plain not configured
459
+                        // Logging error as it is the hardest of the two
460
+                        $this->logger->error(
461
+                            "Unable to send the invitation mail to $email",
462
+                            [
463
+                                'app' => 'ocs_api',
464
+                                'exception' => $e,
465
+                            ]
466
+                        );
467
+                    }
468
+                }
469
+            }
470
+
471
+            return new DataResponse(['id' => $userid]);
472
+        } catch (HintException $e) {
473
+            $this->logger->warning(
474
+                'Failed addUser attempt with hint exception.',
475
+                [
476
+                    'app' => 'ocs_api',
477
+                    'exception' => $e,
478
+                ]
479
+            );
480
+            throw new OCSException($e->getHint(), 107);
481
+        } catch (OCSException $e) {
482
+            $this->logger->warning(
483
+                'Failed addUser attempt with ocs exception.',
484
+                [
485
+                    'app' => 'ocs_api',
486
+                    'exception' => $e,
487
+                ]
488
+            );
489
+            throw $e;
490
+        } catch (InvalidArgumentException $e) {
491
+            $this->logger->error(
492
+                'Failed addUser attempt with invalid argument exception.',
493
+                [
494
+                    'app' => 'ocs_api',
495
+                    'exception' => $e,
496
+                ]
497
+            );
498
+            throw new OCSException($e->getMessage(), 101);
499
+        } catch (\Exception $e) {
500
+            $this->logger->error(
501
+                'Failed addUser attempt with exception.',
502
+                [
503
+                    'app' => 'ocs_api',
504
+                    'exception' => $e
505
+                ]
506
+            );
507
+            throw new OCSException('Bad request', 101);
508
+        }
509
+    }
510
+
511
+    /**
512
+     * @NoAdminRequired
513
+     * @NoSubAdminRequired
514
+     *
515
+     * gets user info
516
+     *
517
+     * @param string $userId
518
+     * @return DataResponse
519
+     * @throws OCSException
520
+     */
521
+    public function getUser(string $userId): DataResponse {
522
+        $includeScopes = false;
523
+        $currentUser = $this->userSession->getUser();
524
+        if ($currentUser && $currentUser->getUID() === $userId) {
525
+            $includeScopes = true;
526
+        }
527
+
528
+        $data = $this->getUserData($userId, $includeScopes);
529
+        // getUserData returns empty array if not enough permissions
530
+        if (empty($data)) {
531
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
532
+        }
533
+        return new DataResponse($data);
534
+    }
535
+
536
+    /**
537
+     * @NoAdminRequired
538
+     * @NoSubAdminRequired
539
+     *
540
+     * gets user info from the currently logged in user
541
+     *
542
+     * @return DataResponse
543
+     * @throws OCSException
544
+     */
545
+    public function getCurrentUser(): DataResponse {
546
+        $user = $this->userSession->getUser();
547
+        if ($user) {
548
+            $data = $this->getUserData($user->getUID(), true);
549
+            // rename "displayname" to "display-name" only for this call to keep
550
+            // the API stable.
551
+            $data['display-name'] = $data['displayname'];
552
+            unset($data['displayname']);
553
+            return new DataResponse($data);
554
+        }
555
+
556
+        throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
557
+    }
558
+
559
+    /**
560
+     * @NoAdminRequired
561
+     * @NoSubAdminRequired
562
+     *
563
+     * @return DataResponse
564
+     * @throws OCSException
565
+     */
566
+    public function getEditableFields(): DataResponse {
567
+        $currentLoggedInUser = $this->userSession->getUser();
568
+        if (!$currentLoggedInUser instanceof IUser) {
569
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
570
+        }
571
+
572
+        return $this->getEditableFieldsForUser($currentLoggedInUser->getUID());
573
+    }
574
+
575
+    /**
576
+     * @NoAdminRequired
577
+     * @NoSubAdminRequired
578
+     *
579
+     * @param string $userId
580
+     * @return DataResponse
581
+     * @throws OCSException
582
+     */
583
+    public function getEditableFieldsForUser(string $userId): DataResponse {
584
+        $currentLoggedInUser = $this->userSession->getUser();
585
+        if (!$currentLoggedInUser instanceof IUser) {
586
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
587
+        }
588
+
589
+        $permittedFields = [];
590
+
591
+        if ($userId !== $currentLoggedInUser->getUID()) {
592
+            $targetUser = $this->userManager->get($userId);
593
+            if (!$targetUser instanceof IUser) {
594
+                throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
595
+            }
596
+
597
+            $subAdminManager = $this->groupManager->getSubAdmin();
598
+            if (
599
+                !$this->groupManager->isAdmin($currentLoggedInUser->getUID())
600
+                && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
601
+            ) {
602
+                throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
603
+            }
604
+        } else {
605
+            $targetUser = $currentLoggedInUser;
606
+        }
607
+
608
+        // Editing self (display, email)
609
+        if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
610
+            if (
611
+                $targetUser->getBackend() instanceof ISetDisplayNameBackend
612
+                || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
613
+            ) {
614
+                $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
615
+            }
616
+            $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
617
+        }
618
+
619
+        $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
620
+        $permittedFields[] = IAccountManager::PROPERTY_PHONE;
621
+        $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
622
+        $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
623
+        $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
624
+        $permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
625
+        $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
626
+        $permittedFields[] = IAccountManager::PROPERTY_ROLE;
627
+        $permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
628
+        $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
629
+        $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
630
+
631
+        return new DataResponse($permittedFields);
632
+    }
633
+
634
+    /**
635
+     * @NoAdminRequired
636
+     * @NoSubAdminRequired
637
+     * @PasswordConfirmationRequired
638
+     *
639
+     * @throws OCSException
640
+     */
641
+    public function editUserMultiValue(
642
+        string $userId,
643
+        string $collectionName,
644
+        string $key,
645
+        string $value
646
+    ): DataResponse {
647
+        $currentLoggedInUser = $this->userSession->getUser();
648
+        if ($currentLoggedInUser === null) {
649
+            throw new OCSException('', OCSController::RESPOND_UNAUTHORISED);
650
+        }
651
+
652
+        $targetUser = $this->userManager->get($userId);
653
+        if ($targetUser === null) {
654
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
655
+        }
656
+
657
+        $subAdminManager = $this->groupManager->getSubAdmin();
658
+        $isAdminOrSubadmin = $this->groupManager->isAdmin($currentLoggedInUser->getUID())
659
+            || $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser);
660
+
661
+        $permittedFields = [];
662
+        if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
663
+            // Editing self (display, email)
664
+            $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
665
+            $permittedFields[] = IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX;
666
+        } else {
667
+            // Check if admin / subadmin
668
+            if ($isAdminOrSubadmin) {
669
+                // They have permissions over the user
670
+                $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
671
+            } else {
672
+                // No rights
673
+                throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
674
+            }
675
+        }
676
+
677
+        // Check if permitted to edit this field
678
+        if (!in_array($collectionName, $permittedFields)) {
679
+            throw new OCSException('', 103);
680
+        }
681
+
682
+        switch ($collectionName) {
683
+            case IAccountManager::COLLECTION_EMAIL:
684
+                $userAccount = $this->accountManager->getAccount($targetUser);
685
+                $mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
686
+                $mailCollection->removePropertyByValue($key);
687
+                if ($value !== '') {
688
+                    $mailCollection->addPropertyWithDefaults($value);
689
+                    $property = $mailCollection->getPropertyByValue($key);
690
+                    if ($isAdminOrSubadmin && $property) {
691
+                        // admin set mails are auto-verified
692
+                        $property->setLocallyVerified(IAccountManager::VERIFIED);
693
+                    }
694
+                }
695
+                $this->accountManager->updateAccount($userAccount);
696
+                break;
697
+
698
+            case IAccountManager::COLLECTION_EMAIL . self::SCOPE_SUFFIX:
699
+                $userAccount = $this->accountManager->getAccount($targetUser);
700
+                $mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
701
+                $targetProperty = null;
702
+                foreach ($mailCollection->getProperties() as $property) {
703
+                    if ($property->getValue() === $key) {
704
+                        $targetProperty = $property;
705
+                        break;
706
+                    }
707
+                }
708
+                if ($targetProperty instanceof IAccountProperty) {
709
+                    try {
710
+                        $targetProperty->setScope($value);
711
+                        $this->accountManager->updateAccount($userAccount);
712
+                    } catch (InvalidArgumentException $e) {
713
+                        throw new OCSException('', 102);
714
+                    }
715
+                } else {
716
+                    throw new OCSException('', 102);
717
+                }
718
+                break;
719
+
720
+            default:
721
+                throw new OCSException('', 103);
722
+        }
723
+        return new DataResponse();
724
+    }
725
+
726
+    /**
727
+     * @NoAdminRequired
728
+     * @NoSubAdminRequired
729
+     * @PasswordConfirmationRequired
730
+     *
731
+     * edit users
732
+     *
733
+     * @param string $userId
734
+     * @param string $key
735
+     * @param string $value
736
+     * @return DataResponse
737
+     * @throws OCSException
738
+     */
739
+    public function editUser(string $userId, string $key, string $value): DataResponse {
740
+        $currentLoggedInUser = $this->userSession->getUser();
741
+
742
+        $targetUser = $this->userManager->get($userId);
743
+        if ($targetUser === null) {
744
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
745
+        }
746
+
747
+        $permittedFields = [];
748
+        if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
749
+            // Editing self (display, email)
750
+            if ($this->config->getSystemValue('allow_user_to_change_display_name', true) !== false) {
751
+                if (
752
+                    $targetUser->getBackend() instanceof ISetDisplayNameBackend
753
+                    || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
754
+                ) {
755
+                    $permittedFields[] = self::USER_FIELD_DISPLAYNAME;
756
+                    $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
757
+                }
758
+                $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
759
+            }
760
+
761
+            $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX;
762
+            $permittedFields[] = IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX;
763
+
764
+            $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
765
+
766
+            $permittedFields[] = self::USER_FIELD_PASSWORD;
767
+            $permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL;
768
+            if (
769
+                $this->config->getSystemValue('force_language', false) === false ||
770
+                $this->groupManager->isAdmin($currentLoggedInUser->getUID())
771
+            ) {
772
+                $permittedFields[] = self::USER_FIELD_LANGUAGE;
773
+            }
774
+
775
+            if (
776
+                $this->config->getSystemValue('force_locale', false) === false ||
777
+                $this->groupManager->isAdmin($currentLoggedInUser->getUID())
778
+            ) {
779
+                $permittedFields[] = self::USER_FIELD_LOCALE;
780
+            }
781
+
782
+            $permittedFields[] = IAccountManager::PROPERTY_PHONE;
783
+            $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
784
+            $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
785
+            $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
786
+            $permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
787
+            $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
788
+            $permittedFields[] = IAccountManager::PROPERTY_ROLE;
789
+            $permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
790
+            $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
791
+            $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
792
+            $permittedFields[] = IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX;
793
+            $permittedFields[] = IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX;
794
+            $permittedFields[] = IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX;
795
+            $permittedFields[] = IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX;
796
+            $permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE . self::SCOPE_SUFFIX;
797
+            $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX;
798
+            $permittedFields[] = IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX;
799
+            $permittedFields[] = IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX;
800
+            $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX;
801
+            $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX;
802
+
803
+            $permittedFields[] = IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX;
804
+
805
+            // If admin they can edit their own quota
806
+            if ($this->groupManager->isAdmin($currentLoggedInUser->getUID())) {
807
+                $permittedFields[] = self::USER_FIELD_QUOTA;
808
+            }
809
+        } else {
810
+            // Check if admin / subadmin
811
+            $subAdminManager = $this->groupManager->getSubAdmin();
812
+            if (
813
+                $this->groupManager->isAdmin($currentLoggedInUser->getUID())
814
+                || $subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
815
+            ) {
816
+                // They have permissions over the user
817
+                if (
818
+                    $targetUser->getBackend() instanceof ISetDisplayNameBackend
819
+                    || $targetUser->getBackend()->implementsActions(Backend::SET_DISPLAYNAME)
820
+                ) {
821
+                    $permittedFields[] = self::USER_FIELD_DISPLAYNAME;
822
+                    $permittedFields[] = IAccountManager::PROPERTY_DISPLAYNAME;
823
+                }
824
+                $permittedFields[] = IAccountManager::PROPERTY_EMAIL;
825
+                $permittedFields[] = IAccountManager::COLLECTION_EMAIL;
826
+                $permittedFields[] = self::USER_FIELD_PASSWORD;
827
+                $permittedFields[] = self::USER_FIELD_LANGUAGE;
828
+                $permittedFields[] = self::USER_FIELD_LOCALE;
829
+                $permittedFields[] = IAccountManager::PROPERTY_PHONE;
830
+                $permittedFields[] = IAccountManager::PROPERTY_ADDRESS;
831
+                $permittedFields[] = IAccountManager::PROPERTY_WEBSITE;
832
+                $permittedFields[] = IAccountManager::PROPERTY_TWITTER;
833
+                $permittedFields[] = IAccountManager::PROPERTY_FEDIVERSE;
834
+                $permittedFields[] = IAccountManager::PROPERTY_ORGANISATION;
835
+                $permittedFields[] = IAccountManager::PROPERTY_ROLE;
836
+                $permittedFields[] = IAccountManager::PROPERTY_HEADLINE;
837
+                $permittedFields[] = IAccountManager::PROPERTY_BIOGRAPHY;
838
+                $permittedFields[] = IAccountManager::PROPERTY_PROFILE_ENABLED;
839
+                $permittedFields[] = self::USER_FIELD_QUOTA;
840
+                $permittedFields[] = self::USER_FIELD_NOTIFICATION_EMAIL;
841
+            } else {
842
+                // No rights
843
+                throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
844
+            }
845
+        }
846
+        // Check if permitted to edit this field
847
+        if (!in_array($key, $permittedFields)) {
848
+            throw new OCSException('', 103);
849
+        }
850
+        // Process the edit
851
+        switch ($key) {
852
+            case self::USER_FIELD_DISPLAYNAME:
853
+            case IAccountManager::PROPERTY_DISPLAYNAME:
854
+                try {
855
+                    $targetUser->setDisplayName($value);
856
+                } catch (InvalidArgumentException $e) {
857
+                    throw new OCSException($e->getMessage(), 101);
858
+                }
859
+                break;
860
+            case self::USER_FIELD_QUOTA:
861
+                $quota = $value;
862
+                if ($quota !== 'none' && $quota !== 'default') {
863
+                    if (is_numeric($quota)) {
864
+                        $quota = (float) $quota;
865
+                    } else {
866
+                        $quota = \OCP\Util::computerFileSize($quota);
867
+                    }
868
+                    if ($quota === false) {
869
+                        throw new OCSException('Invalid quota value ' . $value, 102);
870
+                    }
871
+                    if ($quota === -1) {
872
+                        $quota = 'none';
873
+                    } else {
874
+                        $maxQuota = (int) $this->config->getAppValue('files', 'max_quota', '-1');
875
+                        if ($maxQuota !== -1 && $quota > $maxQuota) {
876
+                            throw new OCSException('Invalid quota value. ' . $value . ' is exceeding the maximum quota', 102);
877
+                        }
878
+                        $quota = \OCP\Util::humanFileSize($quota);
879
+                    }
880
+                }
881
+                // no else block because quota can be set to 'none' in previous if
882
+                if ($quota === 'none') {
883
+                    $allowUnlimitedQuota = $this->config->getAppValue('files', 'allow_unlimited_quota', '1') === '1';
884
+                    if (!$allowUnlimitedQuota) {
885
+                        throw new OCSException('Unlimited quota is forbidden on this instance', 102);
886
+                    }
887
+                }
888
+                $targetUser->setQuota($quota);
889
+                break;
890
+            case self::USER_FIELD_PASSWORD:
891
+                try {
892
+                    if (strlen($value) > IUserManager::MAX_PASSWORD_LENGTH) {
893
+                        throw new OCSException('Invalid password value', 102);
894
+                    }
895
+                    if (!$targetUser->canChangePassword()) {
896
+                        throw new OCSException('Setting the password is not supported by the users backend', 103);
897
+                    }
898
+                    $targetUser->setPassword($value);
899
+                } catch (HintException $e) { // password policy error
900
+                    throw new OCSException($e->getMessage(), 103);
901
+                }
902
+                break;
903
+            case self::USER_FIELD_LANGUAGE:
904
+                $languagesCodes = $this->l10nFactory->findAvailableLanguages();
905
+                if (!in_array($value, $languagesCodes, true) && $value !== 'en') {
906
+                    throw new OCSException('Invalid language', 102);
907
+                }
908
+                $this->config->setUserValue($targetUser->getUID(), 'core', 'lang', $value);
909
+                break;
910
+            case self::USER_FIELD_LOCALE:
911
+                if (!$this->l10nFactory->localeExists($value)) {
912
+                    throw new OCSException('Invalid locale', 102);
913
+                }
914
+                $this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value);
915
+                break;
916
+            case self::USER_FIELD_NOTIFICATION_EMAIL:
917
+                $success = false;
918
+                if ($value === '' || filter_var($value, FILTER_VALIDATE_EMAIL)) {
919
+                    try {
920
+                        $targetUser->setPrimaryEMailAddress($value);
921
+                        $success = true;
922
+                    } catch (InvalidArgumentException $e) {
923
+                        $this->logger->info(
924
+                            'Cannot set primary email, because provided address is not verified',
925
+                            [
926
+                                'app' => 'provisioning_api',
927
+                                'exception' => $e,
928
+                            ]
929
+                        );
930
+                    }
931
+                }
932
+                if (!$success) {
933
+                    throw new OCSException('', 102);
934
+                }
935
+                break;
936
+            case IAccountManager::PROPERTY_EMAIL:
937
+                if (filter_var($value, FILTER_VALIDATE_EMAIL) || $value === '') {
938
+                    $targetUser->setEMailAddress($value);
939
+                } else {
940
+                    throw new OCSException('', 102);
941
+                }
942
+                break;
943
+            case IAccountManager::COLLECTION_EMAIL:
944
+                if (filter_var($value, FILTER_VALIDATE_EMAIL) && $value !== $targetUser->getSystemEMailAddress()) {
945
+                    $userAccount = $this->accountManager->getAccount($targetUser);
946
+                    $mailCollection = $userAccount->getPropertyCollection(IAccountManager::COLLECTION_EMAIL);
947
+                    foreach ($mailCollection->getProperties() as $property) {
948
+                        if ($property->getValue() === $value) {
949
+                            break;
950
+                        }
951
+                    }
952
+                    $mailCollection->addPropertyWithDefaults($value);
953
+                    $this->accountManager->updateAccount($userAccount);
954
+                } else {
955
+                    throw new OCSException('', 102);
956
+                }
957
+                break;
958
+            case IAccountManager::PROPERTY_PHONE:
959
+            case IAccountManager::PROPERTY_ADDRESS:
960
+            case IAccountManager::PROPERTY_WEBSITE:
961
+            case IAccountManager::PROPERTY_TWITTER:
962
+            case IAccountManager::PROPERTY_FEDIVERSE:
963
+            case IAccountManager::PROPERTY_ORGANISATION:
964
+            case IAccountManager::PROPERTY_ROLE:
965
+            case IAccountManager::PROPERTY_HEADLINE:
966
+            case IAccountManager::PROPERTY_BIOGRAPHY:
967
+                $userAccount = $this->accountManager->getAccount($targetUser);
968
+                try {
969
+                    $userProperty = $userAccount->getProperty($key);
970
+                    if ($userProperty->getValue() !== $value) {
971
+                        try {
972
+                            $userProperty->setValue($value);
973
+                            if ($userProperty->getName() === IAccountManager::PROPERTY_PHONE) {
974
+                                $this->knownUserService->deleteByContactUserId($targetUser->getUID());
975
+                            }
976
+                        } catch (InvalidArgumentException $e) {
977
+                            throw new OCSException('Invalid ' . $e->getMessage(), 102);
978
+                        }
979
+                    }
980
+                } catch (PropertyDoesNotExistException $e) {
981
+                    $userAccount->setProperty($key, $value, IAccountManager::SCOPE_PRIVATE, IAccountManager::NOT_VERIFIED);
982
+                }
983
+                try {
984
+                    $this->accountManager->updateAccount($userAccount);
985
+                } catch (InvalidArgumentException $e) {
986
+                    throw new OCSException('Invalid ' . $e->getMessage(), 102);
987
+                }
988
+                break;
989
+            case IAccountManager::PROPERTY_PROFILE_ENABLED:
990
+                $userAccount = $this->accountManager->getAccount($targetUser);
991
+                try {
992
+                    $userProperty = $userAccount->getProperty($key);
993
+                    if ($userProperty->getValue() !== $value) {
994
+                        $userProperty->setValue($value);
995
+                    }
996
+                } catch (PropertyDoesNotExistException $e) {
997
+                    $userAccount->setProperty($key, $value, IAccountManager::SCOPE_LOCAL, IAccountManager::NOT_VERIFIED);
998
+                }
999
+                $this->accountManager->updateAccount($userAccount);
1000
+                break;
1001
+            case IAccountManager::PROPERTY_DISPLAYNAME . self::SCOPE_SUFFIX:
1002
+            case IAccountManager::PROPERTY_EMAIL . self::SCOPE_SUFFIX:
1003
+            case IAccountManager::PROPERTY_PHONE . self::SCOPE_SUFFIX:
1004
+            case IAccountManager::PROPERTY_ADDRESS . self::SCOPE_SUFFIX:
1005
+            case IAccountManager::PROPERTY_WEBSITE . self::SCOPE_SUFFIX:
1006
+            case IAccountManager::PROPERTY_TWITTER . self::SCOPE_SUFFIX:
1007
+            case IAccountManager::PROPERTY_FEDIVERSE . self::SCOPE_SUFFIX:
1008
+            case IAccountManager::PROPERTY_ORGANISATION . self::SCOPE_SUFFIX:
1009
+            case IAccountManager::PROPERTY_ROLE . self::SCOPE_SUFFIX:
1010
+            case IAccountManager::PROPERTY_HEADLINE . self::SCOPE_SUFFIX:
1011
+            case IAccountManager::PROPERTY_BIOGRAPHY . self::SCOPE_SUFFIX:
1012
+            case IAccountManager::PROPERTY_PROFILE_ENABLED . self::SCOPE_SUFFIX:
1013
+            case IAccountManager::PROPERTY_AVATAR . self::SCOPE_SUFFIX:
1014
+                $propertyName = substr($key, 0, strlen($key) - strlen(self::SCOPE_SUFFIX));
1015
+                $userAccount = $this->accountManager->getAccount($targetUser);
1016
+                $userProperty = $userAccount->getProperty($propertyName);
1017
+                if ($userProperty->getScope() !== $value) {
1018
+                    try {
1019
+                        $userProperty->setScope($value);
1020
+                        $this->accountManager->updateAccount($userAccount);
1021
+                    } catch (InvalidArgumentException $e) {
1022
+                        throw new OCSException('Invalid ' . $e->getMessage(), 102);
1023
+                    }
1024
+                }
1025
+                break;
1026
+            default:
1027
+                throw new OCSException('', 103);
1028
+        }
1029
+        return new DataResponse();
1030
+    }
1031
+
1032
+    /**
1033
+     * @PasswordConfirmationRequired
1034
+     * @NoAdminRequired
1035
+     *
1036
+     * @param string $userId
1037
+     *
1038
+     * @return DataResponse
1039
+     *
1040
+     * @throws OCSException
1041
+     */
1042
+    public function wipeUserDevices(string $userId): DataResponse {
1043
+        /** @var IUser $currentLoggedInUser */
1044
+        $currentLoggedInUser = $this->userSession->getUser();
1045
+
1046
+        $targetUser = $this->userManager->get($userId);
1047
+
1048
+        if ($targetUser === null) {
1049
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1050
+        }
1051
+
1052
+        if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
1053
+            throw new OCSException('', 101);
1054
+        }
1055
+
1056
+        // If not permitted
1057
+        $subAdminManager = $this->groupManager->getSubAdmin();
1058
+        if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
1059
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1060
+        }
1061
+
1062
+        $this->remoteWipe->markAllTokensForWipe($targetUser);
1063
+
1064
+        return new DataResponse();
1065
+    }
1066
+
1067
+    /**
1068
+     * @PasswordConfirmationRequired
1069
+     * @NoAdminRequired
1070
+     *
1071
+     * @param string $userId
1072
+     * @return DataResponse
1073
+     * @throws OCSException
1074
+     */
1075
+    public function deleteUser(string $userId): DataResponse {
1076
+        $currentLoggedInUser = $this->userSession->getUser();
1077
+
1078
+        $targetUser = $this->userManager->get($userId);
1079
+
1080
+        if ($targetUser === null) {
1081
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1082
+        }
1083
+
1084
+        if ($targetUser->getUID() === $currentLoggedInUser->getUID()) {
1085
+            throw new OCSException('', 101);
1086
+        }
1087
+
1088
+        // If not permitted
1089
+        $subAdminManager = $this->groupManager->getSubAdmin();
1090
+        if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
1091
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1092
+        }
1093
+
1094
+        // Go ahead with the delete
1095
+        if ($targetUser->delete()) {
1096
+            return new DataResponse();
1097
+        } else {
1098
+            throw new OCSException('', 101);
1099
+        }
1100
+    }
1101
+
1102
+    /**
1103
+     * @PasswordConfirmationRequired
1104
+     * @NoAdminRequired
1105
+     *
1106
+     * @param string $userId
1107
+     * @return DataResponse
1108
+     * @throws OCSException
1109
+     * @throws OCSForbiddenException
1110
+     */
1111
+    public function disableUser(string $userId): DataResponse {
1112
+        return $this->setEnabled($userId, false);
1113
+    }
1114
+
1115
+    /**
1116
+     * @PasswordConfirmationRequired
1117
+     * @NoAdminRequired
1118
+     *
1119
+     * @param string $userId
1120
+     * @return DataResponse
1121
+     * @throws OCSException
1122
+     * @throws OCSForbiddenException
1123
+     */
1124
+    public function enableUser(string $userId): DataResponse {
1125
+        return $this->setEnabled($userId, true);
1126
+    }
1127
+
1128
+    /**
1129
+     * @param string $userId
1130
+     * @param bool $value
1131
+     * @return DataResponse
1132
+     * @throws OCSException
1133
+     */
1134
+    private function setEnabled(string $userId, bool $value): DataResponse {
1135
+        $currentLoggedInUser = $this->userSession->getUser();
1136
+
1137
+        $targetUser = $this->userManager->get($userId);
1138
+        if ($targetUser === null || $targetUser->getUID() === $currentLoggedInUser->getUID()) {
1139
+            throw new OCSException('', 101);
1140
+        }
1141
+
1142
+        // If not permitted
1143
+        $subAdminManager = $this->groupManager->getSubAdmin();
1144
+        if (!$this->groupManager->isAdmin($currentLoggedInUser->getUID()) && !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)) {
1145
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1146
+        }
1147
+
1148
+        // enable/disable the user now
1149
+        $targetUser->setEnabled($value);
1150
+        return new DataResponse();
1151
+    }
1152
+
1153
+    /**
1154
+     * @NoAdminRequired
1155
+     * @NoSubAdminRequired
1156
+     *
1157
+     * @param string $userId
1158
+     * @return DataResponse
1159
+     * @throws OCSException
1160
+     */
1161
+    public function getUsersGroups(string $userId): DataResponse {
1162
+        $loggedInUser = $this->userSession->getUser();
1163
+
1164
+        $targetUser = $this->userManager->get($userId);
1165
+        if ($targetUser === null) {
1166
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1167
+        }
1168
+
1169
+        if ($targetUser->getUID() === $loggedInUser->getUID() || $this->groupManager->isAdmin($loggedInUser->getUID())) {
1170
+            // Self lookup or admin lookup
1171
+            return new DataResponse([
1172
+                'groups' => $this->groupManager->getUserGroupIds($targetUser)
1173
+            ]);
1174
+        } else {
1175
+            $subAdminManager = $this->groupManager->getSubAdmin();
1176
+
1177
+            // Looking up someone else
1178
+            if ($subAdminManager->isUserAccessible($loggedInUser, $targetUser)) {
1179
+                // Return the group that the method caller is subadmin of for the user in question
1180
+                /** @var IGroup[] $getSubAdminsGroups */
1181
+                $getSubAdminsGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
1182
+                foreach ($getSubAdminsGroups as $key => $group) {
1183
+                    $getSubAdminsGroups[$key] = $group->getGID();
1184
+                }
1185
+                $groups = array_intersect(
1186
+                    $getSubAdminsGroups,
1187
+                    $this->groupManager->getUserGroupIds($targetUser)
1188
+                );
1189
+                return new DataResponse(['groups' => $groups]);
1190
+            } else {
1191
+                // Not permitted
1192
+                throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1193
+            }
1194
+        }
1195
+    }
1196
+
1197
+    /**
1198
+     * @PasswordConfirmationRequired
1199
+     * @NoAdminRequired
1200
+     *
1201
+     * @param string $userId
1202
+     * @param string $groupid
1203
+     * @return DataResponse
1204
+     * @throws OCSException
1205
+     */
1206
+    public function addToGroup(string $userId, string $groupid = ''): DataResponse {
1207
+        if ($groupid === '') {
1208
+            throw new OCSException('', 101);
1209
+        }
1210
+
1211
+        $group = $this->groupManager->get($groupid);
1212
+        $targetUser = $this->userManager->get($userId);
1213
+        if ($group === null) {
1214
+            throw new OCSException('', 102);
1215
+        }
1216
+        if ($targetUser === null) {
1217
+            throw new OCSException('', 103);
1218
+        }
1219
+
1220
+        // If they're not an admin, check they are a subadmin of the group in question
1221
+        $loggedInUser = $this->userSession->getUser();
1222
+        $subAdminManager = $this->groupManager->getSubAdmin();
1223
+        if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
1224
+            throw new OCSException('', 104);
1225
+        }
1226
+
1227
+        // Add user to group
1228
+        $group->addUser($targetUser);
1229
+        return new DataResponse();
1230
+    }
1231
+
1232
+    /**
1233
+     * @PasswordConfirmationRequired
1234
+     * @NoAdminRequired
1235
+     *
1236
+     * @param string $userId
1237
+     * @param string $groupid
1238
+     * @return DataResponse
1239
+     * @throws OCSException
1240
+     */
1241
+    public function removeFromGroup(string $userId, string $groupid): DataResponse {
1242
+        $loggedInUser = $this->userSession->getUser();
1243
+
1244
+        if ($groupid === null || trim($groupid) === '') {
1245
+            throw new OCSException('', 101);
1246
+        }
1247
+
1248
+        $group = $this->groupManager->get($groupid);
1249
+        if ($group === null) {
1250
+            throw new OCSException('', 102);
1251
+        }
1252
+
1253
+        $targetUser = $this->userManager->get($userId);
1254
+        if ($targetUser === null) {
1255
+            throw new OCSException('', 103);
1256
+        }
1257
+
1258
+        // If they're not an admin, check they are a subadmin of the group in question
1259
+        $subAdminManager = $this->groupManager->getSubAdmin();
1260
+        if (!$this->groupManager->isAdmin($loggedInUser->getUID()) && !$subAdminManager->isSubAdminOfGroup($loggedInUser, $group)) {
1261
+            throw new OCSException('', 104);
1262
+        }
1263
+
1264
+        // Check they aren't removing themselves from 'admin' or their 'subadmin; group
1265
+        if ($targetUser->getUID() === $loggedInUser->getUID()) {
1266
+            if ($this->groupManager->isAdmin($loggedInUser->getUID())) {
1267
+                if ($group->getGID() === 'admin') {
1268
+                    throw new OCSException('Cannot remove yourself from the admin group', 105);
1269
+                }
1270
+            } else {
1271
+                // Not an admin, so the user must be a subadmin of this group, but that is not allowed.
1272
+                throw new OCSException('Cannot remove yourself from this group as you are a SubAdmin', 105);
1273
+            }
1274
+        } elseif (!$this->groupManager->isAdmin($loggedInUser->getUID())) {
1275
+            /** @var IGroup[] $subAdminGroups */
1276
+            $subAdminGroups = $subAdminManager->getSubAdminsGroups($loggedInUser);
1277
+            $subAdminGroups = array_map(function (IGroup $subAdminGroup) {
1278
+                return $subAdminGroup->getGID();
1279
+            }, $subAdminGroups);
1280
+            $userGroups = $this->groupManager->getUserGroupIds($targetUser);
1281
+            $userSubAdminGroups = array_intersect($subAdminGroups, $userGroups);
1282
+
1283
+            if (count($userSubAdminGroups) <= 1) {
1284
+                // Subadmin must not be able to remove a user from all their subadmin groups.
1285
+                throw new OCSException('Not viable to remove user from the last group you are SubAdmin of', 105);
1286
+            }
1287
+        }
1288
+
1289
+        // Remove user from group
1290
+        $group->removeUser($targetUser);
1291
+        return new DataResponse();
1292
+    }
1293
+
1294
+    /**
1295
+     * Creates a subadmin
1296
+     *
1297
+     * @PasswordConfirmationRequired
1298
+     *
1299
+     * @param string $userId
1300
+     * @param string $groupid
1301
+     * @return DataResponse
1302
+     * @throws OCSException
1303
+     */
1304
+    public function addSubAdmin(string $userId, string $groupid): DataResponse {
1305
+        $group = $this->groupManager->get($groupid);
1306
+        $user = $this->userManager->get($userId);
1307
+
1308
+        // Check if the user exists
1309
+        if ($user === null) {
1310
+            throw new OCSException('User does not exist', 101);
1311
+        }
1312
+        // Check if group exists
1313
+        if ($group === null) {
1314
+            throw new OCSException('Group does not exist',  102);
1315
+        }
1316
+        // Check if trying to make subadmin of admin group
1317
+        if ($group->getGID() === 'admin') {
1318
+            throw new OCSException('Cannot create subadmins for admin group', 103);
1319
+        }
1320
+
1321
+        $subAdminManager = $this->groupManager->getSubAdmin();
1322
+
1323
+        // We cannot be subadmin twice
1324
+        if ($subAdminManager->isSubAdminOfGroup($user, $group)) {
1325
+            return new DataResponse();
1326
+        }
1327
+        // Go
1328
+        $subAdminManager->createSubAdmin($user, $group);
1329
+        return new DataResponse();
1330
+    }
1331
+
1332
+    /**
1333
+     * Removes a subadmin from a group
1334
+     *
1335
+     * @PasswordConfirmationRequired
1336
+     *
1337
+     * @param string $userId
1338
+     * @param string $groupid
1339
+     * @return DataResponse
1340
+     * @throws OCSException
1341
+     */
1342
+    public function removeSubAdmin(string $userId, string $groupid): DataResponse {
1343
+        $group = $this->groupManager->get($groupid);
1344
+        $user = $this->userManager->get($userId);
1345
+        $subAdminManager = $this->groupManager->getSubAdmin();
1346
+
1347
+        // Check if the user exists
1348
+        if ($user === null) {
1349
+            throw new OCSException('User does not exist', 101);
1350
+        }
1351
+        // Check if the group exists
1352
+        if ($group === null) {
1353
+            throw new OCSException('Group does not exist', 101);
1354
+        }
1355
+        // Check if they are a subadmin of this said group
1356
+        if (!$subAdminManager->isSubAdminOfGroup($user, $group)) {
1357
+            throw new OCSException('User is not a subadmin of this group', 102);
1358
+        }
1359
+
1360
+        // Go
1361
+        $subAdminManager->deleteSubAdmin($user, $group);
1362
+        return new DataResponse();
1363
+    }
1364
+
1365
+    /**
1366
+     * Get the groups a user is a subadmin of
1367
+     *
1368
+     * @param string $userId
1369
+     * @return DataResponse
1370
+     * @throws OCSException
1371
+     */
1372
+    public function getUserSubAdminGroups(string $userId): DataResponse {
1373
+        $groups = $this->getUserSubAdminGroupsData($userId);
1374
+        return new DataResponse($groups);
1375
+    }
1376
+
1377
+    /**
1378
+     * @NoAdminRequired
1379
+     * @PasswordConfirmationRequired
1380
+     *
1381
+     * resend welcome message
1382
+     *
1383
+     * @param string $userId
1384
+     * @return DataResponse
1385
+     * @throws OCSException
1386
+     */
1387
+    public function resendWelcomeMessage(string $userId): DataResponse {
1388
+        $currentLoggedInUser = $this->userSession->getUser();
1389
+
1390
+        $targetUser = $this->userManager->get($userId);
1391
+        if ($targetUser === null) {
1392
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1393
+        }
1394
+
1395
+        // Check if admin / subadmin
1396
+        $subAdminManager = $this->groupManager->getSubAdmin();
1397
+        if (
1398
+            !$subAdminManager->isUserAccessible($currentLoggedInUser, $targetUser)
1399
+            && !$this->groupManager->isAdmin($currentLoggedInUser->getUID())
1400
+        ) {
1401
+            // No rights
1402
+            throw new OCSException('', OCSController::RESPOND_NOT_FOUND);
1403
+        }
1404
+
1405
+        $email = $targetUser->getEMailAddress();
1406
+        if ($email === '' || $email === null) {
1407
+            throw new OCSException('Email address not available', 101);
1408
+        }
1409
+
1410
+        try {
1411
+            $emailTemplate = $this->newUserMailHelper->generateTemplate($targetUser, false);
1412
+            $this->newUserMailHelper->sendMail($targetUser, $emailTemplate);
1413
+        } catch (\Exception $e) {
1414
+            $this->logger->error(
1415
+                "Can't send new user mail to $email",
1416
+                [
1417
+                    'app' => 'settings',
1418
+                    'exception' => $e,
1419
+                ]
1420
+            );
1421
+            throw new OCSException('Sending email failed', 102);
1422
+        }
1423
+
1424
+        return new DataResponse();
1425
+    }
1426 1426
 }
Please login to merge, or discard this patch.
core/Controller/LostController.php 1 patch
Indentation   +272 added lines, -272 removed lines patch added patch discarded remove patch
@@ -73,276 +73,276 @@
 block discarded – undo
73 73
  * @package OC\Core\Controller
74 74
  */
75 75
 class LostController extends Controller {
76
-	protected IURLGenerator $urlGenerator;
77
-	protected IUserManager $userManager;
78
-	protected Defaults $defaults;
79
-	protected IL10N $l10n;
80
-	protected string $from;
81
-	protected IManager $encryptionManager;
82
-	protected IConfig $config;
83
-	protected IMailer $mailer;
84
-	private LoggerInterface $logger;
85
-	private Manager $twoFactorManager;
86
-	private IInitialState $initialState;
87
-	private IVerificationToken $verificationToken;
88
-	private IEventDispatcher $eventDispatcher;
89
-	private Limiter $limiter;
90
-
91
-	public function __construct(
92
-		string $appName,
93
-		IRequest $request,
94
-		IURLGenerator $urlGenerator,
95
-		IUserManager $userManager,
96
-		Defaults $defaults,
97
-		IL10N $l10n,
98
-		IConfig $config,
99
-		string $defaultMailAddress,
100
-		IManager $encryptionManager,
101
-		IMailer $mailer,
102
-		LoggerInterface $logger,
103
-		Manager $twoFactorManager,
104
-		IInitialState $initialState,
105
-		IVerificationToken $verificationToken,
106
-		IEventDispatcher $eventDispatcher,
107
-		Limiter $limiter
108
-	) {
109
-		parent::__construct($appName, $request);
110
-		$this->urlGenerator = $urlGenerator;
111
-		$this->userManager = $userManager;
112
-		$this->defaults = $defaults;
113
-		$this->l10n = $l10n;
114
-		$this->from = $defaultMailAddress;
115
-		$this->encryptionManager = $encryptionManager;
116
-		$this->config = $config;
117
-		$this->mailer = $mailer;
118
-		$this->logger = $logger;
119
-		$this->twoFactorManager = $twoFactorManager;
120
-		$this->initialState = $initialState;
121
-		$this->verificationToken = $verificationToken;
122
-		$this->eventDispatcher = $eventDispatcher;
123
-		$this->limiter = $limiter;
124
-	}
125
-
126
-	/**
127
-	 * Someone wants to reset their password:
128
-	 *
129
-	 * @PublicPage
130
-	 * @NoCSRFRequired
131
-	 */
132
-	public function resetform(string $token, string $userId): TemplateResponse {
133
-		try {
134
-			$this->checkPasswordResetToken($token, $userId);
135
-		} catch (Exception $e) {
136
-			if ($this->config->getSystemValue('lost_password_link', '') !== 'disabled'
137
-				|| ($e instanceof InvalidTokenException
138
-					&& !in_array($e->getCode(), [InvalidTokenException::TOKEN_NOT_FOUND, InvalidTokenException::USER_UNKNOWN]))
139
-			) {
140
-				return new TemplateResponse(
141
-					'core', 'error', [
142
-						"errors" => [["error" => $e->getMessage()]]
143
-					],
144
-					TemplateResponse::RENDER_AS_GUEST
145
-				);
146
-			}
147
-			return new TemplateResponse('core', 'error', [
148
-				'errors' => [['error' => $this->l10n->t('Password reset is disabled')]]
149
-			],
150
-				TemplateResponse::RENDER_AS_GUEST
151
-			);
152
-		}
153
-		$this->initialState->provideInitialState('resetPasswordUser', $userId);
154
-		$this->initialState->provideInitialState('resetPasswordTarget',
155
-			$this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', ['userId' => $userId, 'token' => $token])
156
-		);
157
-
158
-		return new TemplateResponse(
159
-			'core',
160
-			'login',
161
-			[],
162
-			'guest'
163
-		);
164
-	}
165
-
166
-	/**
167
-	 * @throws Exception
168
-	 */
169
-	protected function checkPasswordResetToken(string $token, string $userId): void {
170
-		try {
171
-			$user = $this->userManager->get($userId);
172
-			$this->verificationToken->check($token, $user, 'lostpassword', $user ? $user->getEMailAddress() : '', true);
173
-		} catch (InvalidTokenException $e) {
174
-			$error = $e->getCode() === InvalidTokenException::TOKEN_EXPIRED
175
-				? $this->l10n->t('Could not reset password because the token is expired')
176
-				: $this->l10n->t('Could not reset password because the token is invalid');
177
-			throw new Exception($error, (int)$e->getCode(), $e);
178
-		}
179
-	}
180
-
181
-	private function error(string $message, array $additional = []): array {
182
-		return array_merge(['status' => 'error', 'msg' => $message], $additional);
183
-	}
184
-
185
-	private function success(array $data = []): array {
186
-		return array_merge($data, ['status' => 'success']);
187
-	}
188
-
189
-	/**
190
-	 * @PublicPage
191
-	 * @BruteForceProtection(action=passwordResetEmail)
192
-	 * @AnonRateThrottle(limit=10, period=300)
193
-	 */
194
-	public function email(string $user): JSONResponse {
195
-		if ($this->config->getSystemValue('lost_password_link', '') !== '') {
196
-			return new JSONResponse($this->error($this->l10n->t('Password reset is disabled')));
197
-		}
198
-
199
-		\OCP\Util::emitHook(
200
-			'\OCA\Files_Sharing\API\Server2Server',
201
-			'preLoginNameUsedAsUserName',
202
-			['uid' => &$user]
203
-		);
204
-
205
-		// FIXME: use HTTP error codes
206
-		try {
207
-			$this->sendEmail($user);
208
-		} catch (ResetPasswordException $e) {
209
-			// Ignore the error since we do not want to leak this info
210
-			$this->logger->warning('Could not send password reset email: ' . $e->getMessage());
211
-		} catch (Exception $e) {
212
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
213
-		}
214
-
215
-		$response = new JSONResponse($this->success());
216
-		$response->throttle();
217
-		return $response;
218
-	}
219
-
220
-	/**
221
-	 * @PublicPage
222
-	 */
223
-	public function setPassword(string $token, string $userId, string $password, bool $proceed): array {
224
-		if ($this->encryptionManager->isEnabled() && !$proceed) {
225
-			$encryptionModules = $this->encryptionManager->getEncryptionModules();
226
-			foreach ($encryptionModules as $module) {
227
-				/** @var IEncryptionModule $instance */
228
-				$instance = call_user_func($module['callback']);
229
-				// this way we can find out whether per-user keys are used or a system wide encryption key
230
-				if ($instance->needDetailedAccessList()) {
231
-					return $this->error('', ['encryption' => true]);
232
-				}
233
-			}
234
-		}
235
-
236
-		try {
237
-			$this->checkPasswordResetToken($token, $userId);
238
-			$user = $this->userManager->get($userId);
239
-
240
-			$this->eventDispatcher->dispatchTyped(new BeforePasswordResetEvent($user, $password));
241
-			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', ['uid' => $userId, 'password' => $password]);
242
-
243
-			if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
244
-				throw new HintException('Password too long', $this->l10n->t('Password is too long. Maximum allowed length is 469 characters.'));
245
-			}
246
-
247
-			if (!$user->setPassword($password)) {
248
-				throw new Exception();
249
-			}
250
-
251
-			$this->eventDispatcher->dispatchTyped(new PasswordResetEvent($user, $password));
252
-			\OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', ['uid' => $userId, 'password' => $password]);
253
-
254
-			$this->twoFactorManager->clearTwoFactorPending($userId);
255
-
256
-			$this->config->deleteUserValue($userId, 'core', 'lostpassword');
257
-			@\OC::$server->getUserSession()->unsetMagicInCookie();
258
-		} catch (HintException $e) {
259
-			return $this->error($e->getHint());
260
-		} catch (Exception $e) {
261
-			return $this->error($e->getMessage());
262
-		}
263
-
264
-		return $this->success(['user' => $userId]);
265
-	}
266
-
267
-	/**
268
-	 * @throws ResetPasswordException
269
-	 * @throws \OCP\PreConditionNotMetException
270
-	 */
271
-	protected function sendEmail(string $input): void {
272
-		$user = $this->findUserByIdOrMail($input);
273
-		$email = $user->getEMailAddress();
274
-
275
-		if (empty($email)) {
276
-			throw new ResetPasswordException('Could not send reset e-mail since there is no email for username ' . $input);
277
-		}
278
-
279
-		try {
280
-			$this->limiter->registerUserRequest('lostpasswordemail', 5, 1800, $user);
281
-		} catch (RateLimitExceededException $e) {
282
-			throw new ResetPasswordException('Could not send reset e-mail, 5 of them were already sent in the last 30 minutes', 0, $e);
283
-		}
284
-
285
-		// Generate the token. It is stored encrypted in the database with the
286
-		// secret being the users' email address appended with the system secret.
287
-		// This makes the token automatically invalidate once the user changes
288
-		// their email address.
289
-		$token = $this->verificationToken->create($user, 'lostpassword', $email);
290
-
291
-		$link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $user->getUID(), 'token' => $token]);
292
-
293
-		$emailTemplate = $this->mailer->createEMailTemplate('core.ResetPassword', [
294
-			'link' => $link,
295
-		]);
296
-
297
-		$emailTemplate->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
298
-		$emailTemplate->addHeader();
299
-		$emailTemplate->addHeading($this->l10n->t('Password reset'));
300
-
301
-		$emailTemplate->addBodyText(
302
-			htmlspecialchars($this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.')),
303
-			$this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
304
-		);
305
-
306
-		$emailTemplate->addBodyButton(
307
-			htmlspecialchars($this->l10n->t('Reset your password')),
308
-			$link,
309
-			false
310
-		);
311
-		$emailTemplate->addFooter();
312
-
313
-		try {
314
-			$message = $this->mailer->createMessage();
315
-			$message->setTo([$email => $user->getDisplayName()]);
316
-			$message->setFrom([$this->from => $this->defaults->getName()]);
317
-			$message->useTemplate($emailTemplate);
318
-			$this->mailer->send($message);
319
-		} catch (Exception $e) {
320
-			// Log the exception and continue
321
-			$this->logger->error($e->getMessage(), ['app' => 'core', 'exception' => $e]);
322
-		}
323
-	}
324
-
325
-	/**
326
-	 * @throws ResetPasswordException
327
-	 */
328
-	protected function findUserByIdOrMail(string $input): IUser {
329
-		$user = $this->userManager->get($input);
330
-		if ($user instanceof IUser) {
331
-			if (!$user->isEnabled()) {
332
-				throw new ResetPasswordException('User ' . $user->getUID() . ' is disabled');
333
-			}
334
-
335
-			return $user;
336
-		}
337
-
338
-		$users = array_filter($this->userManager->getByEmail($input), function (IUser $user) {
339
-			return $user->isEnabled();
340
-		});
341
-
342
-		if (count($users) === 1) {
343
-			return reset($users);
344
-		}
345
-
346
-		throw new ResetPasswordException('Could not find user ' . $input);
347
-	}
76
+    protected IURLGenerator $urlGenerator;
77
+    protected IUserManager $userManager;
78
+    protected Defaults $defaults;
79
+    protected IL10N $l10n;
80
+    protected string $from;
81
+    protected IManager $encryptionManager;
82
+    protected IConfig $config;
83
+    protected IMailer $mailer;
84
+    private LoggerInterface $logger;
85
+    private Manager $twoFactorManager;
86
+    private IInitialState $initialState;
87
+    private IVerificationToken $verificationToken;
88
+    private IEventDispatcher $eventDispatcher;
89
+    private Limiter $limiter;
90
+
91
+    public function __construct(
92
+        string $appName,
93
+        IRequest $request,
94
+        IURLGenerator $urlGenerator,
95
+        IUserManager $userManager,
96
+        Defaults $defaults,
97
+        IL10N $l10n,
98
+        IConfig $config,
99
+        string $defaultMailAddress,
100
+        IManager $encryptionManager,
101
+        IMailer $mailer,
102
+        LoggerInterface $logger,
103
+        Manager $twoFactorManager,
104
+        IInitialState $initialState,
105
+        IVerificationToken $verificationToken,
106
+        IEventDispatcher $eventDispatcher,
107
+        Limiter $limiter
108
+    ) {
109
+        parent::__construct($appName, $request);
110
+        $this->urlGenerator = $urlGenerator;
111
+        $this->userManager = $userManager;
112
+        $this->defaults = $defaults;
113
+        $this->l10n = $l10n;
114
+        $this->from = $defaultMailAddress;
115
+        $this->encryptionManager = $encryptionManager;
116
+        $this->config = $config;
117
+        $this->mailer = $mailer;
118
+        $this->logger = $logger;
119
+        $this->twoFactorManager = $twoFactorManager;
120
+        $this->initialState = $initialState;
121
+        $this->verificationToken = $verificationToken;
122
+        $this->eventDispatcher = $eventDispatcher;
123
+        $this->limiter = $limiter;
124
+    }
125
+
126
+    /**
127
+     * Someone wants to reset their password:
128
+     *
129
+     * @PublicPage
130
+     * @NoCSRFRequired
131
+     */
132
+    public function resetform(string $token, string $userId): TemplateResponse {
133
+        try {
134
+            $this->checkPasswordResetToken($token, $userId);
135
+        } catch (Exception $e) {
136
+            if ($this->config->getSystemValue('lost_password_link', '') !== 'disabled'
137
+                || ($e instanceof InvalidTokenException
138
+                    && !in_array($e->getCode(), [InvalidTokenException::TOKEN_NOT_FOUND, InvalidTokenException::USER_UNKNOWN]))
139
+            ) {
140
+                return new TemplateResponse(
141
+                    'core', 'error', [
142
+                        "errors" => [["error" => $e->getMessage()]]
143
+                    ],
144
+                    TemplateResponse::RENDER_AS_GUEST
145
+                );
146
+            }
147
+            return new TemplateResponse('core', 'error', [
148
+                'errors' => [['error' => $this->l10n->t('Password reset is disabled')]]
149
+            ],
150
+                TemplateResponse::RENDER_AS_GUEST
151
+            );
152
+        }
153
+        $this->initialState->provideInitialState('resetPasswordUser', $userId);
154
+        $this->initialState->provideInitialState('resetPasswordTarget',
155
+            $this->urlGenerator->linkToRouteAbsolute('core.lost.setPassword', ['userId' => $userId, 'token' => $token])
156
+        );
157
+
158
+        return new TemplateResponse(
159
+            'core',
160
+            'login',
161
+            [],
162
+            'guest'
163
+        );
164
+    }
165
+
166
+    /**
167
+     * @throws Exception
168
+     */
169
+    protected function checkPasswordResetToken(string $token, string $userId): void {
170
+        try {
171
+            $user = $this->userManager->get($userId);
172
+            $this->verificationToken->check($token, $user, 'lostpassword', $user ? $user->getEMailAddress() : '', true);
173
+        } catch (InvalidTokenException $e) {
174
+            $error = $e->getCode() === InvalidTokenException::TOKEN_EXPIRED
175
+                ? $this->l10n->t('Could not reset password because the token is expired')
176
+                : $this->l10n->t('Could not reset password because the token is invalid');
177
+            throw new Exception($error, (int)$e->getCode(), $e);
178
+        }
179
+    }
180
+
181
+    private function error(string $message, array $additional = []): array {
182
+        return array_merge(['status' => 'error', 'msg' => $message], $additional);
183
+    }
184
+
185
+    private function success(array $data = []): array {
186
+        return array_merge($data, ['status' => 'success']);
187
+    }
188
+
189
+    /**
190
+     * @PublicPage
191
+     * @BruteForceProtection(action=passwordResetEmail)
192
+     * @AnonRateThrottle(limit=10, period=300)
193
+     */
194
+    public function email(string $user): JSONResponse {
195
+        if ($this->config->getSystemValue('lost_password_link', '') !== '') {
196
+            return new JSONResponse($this->error($this->l10n->t('Password reset is disabled')));
197
+        }
198
+
199
+        \OCP\Util::emitHook(
200
+            '\OCA\Files_Sharing\API\Server2Server',
201
+            'preLoginNameUsedAsUserName',
202
+            ['uid' => &$user]
203
+        );
204
+
205
+        // FIXME: use HTTP error codes
206
+        try {
207
+            $this->sendEmail($user);
208
+        } catch (ResetPasswordException $e) {
209
+            // Ignore the error since we do not want to leak this info
210
+            $this->logger->warning('Could not send password reset email: ' . $e->getMessage());
211
+        } catch (Exception $e) {
212
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
213
+        }
214
+
215
+        $response = new JSONResponse($this->success());
216
+        $response->throttle();
217
+        return $response;
218
+    }
219
+
220
+    /**
221
+     * @PublicPage
222
+     */
223
+    public function setPassword(string $token, string $userId, string $password, bool $proceed): array {
224
+        if ($this->encryptionManager->isEnabled() && !$proceed) {
225
+            $encryptionModules = $this->encryptionManager->getEncryptionModules();
226
+            foreach ($encryptionModules as $module) {
227
+                /** @var IEncryptionModule $instance */
228
+                $instance = call_user_func($module['callback']);
229
+                // this way we can find out whether per-user keys are used or a system wide encryption key
230
+                if ($instance->needDetailedAccessList()) {
231
+                    return $this->error('', ['encryption' => true]);
232
+                }
233
+            }
234
+        }
235
+
236
+        try {
237
+            $this->checkPasswordResetToken($token, $userId);
238
+            $user = $this->userManager->get($userId);
239
+
240
+            $this->eventDispatcher->dispatchTyped(new BeforePasswordResetEvent($user, $password));
241
+            \OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'pre_passwordReset', ['uid' => $userId, 'password' => $password]);
242
+
243
+            if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
244
+                throw new HintException('Password too long', $this->l10n->t('Password is too long. Maximum allowed length is 469 characters.'));
245
+            }
246
+
247
+            if (!$user->setPassword($password)) {
248
+                throw new Exception();
249
+            }
250
+
251
+            $this->eventDispatcher->dispatchTyped(new PasswordResetEvent($user, $password));
252
+            \OC_Hook::emit('\OC\Core\LostPassword\Controller\LostController', 'post_passwordReset', ['uid' => $userId, 'password' => $password]);
253
+
254
+            $this->twoFactorManager->clearTwoFactorPending($userId);
255
+
256
+            $this->config->deleteUserValue($userId, 'core', 'lostpassword');
257
+            @\OC::$server->getUserSession()->unsetMagicInCookie();
258
+        } catch (HintException $e) {
259
+            return $this->error($e->getHint());
260
+        } catch (Exception $e) {
261
+            return $this->error($e->getMessage());
262
+        }
263
+
264
+        return $this->success(['user' => $userId]);
265
+    }
266
+
267
+    /**
268
+     * @throws ResetPasswordException
269
+     * @throws \OCP\PreConditionNotMetException
270
+     */
271
+    protected function sendEmail(string $input): void {
272
+        $user = $this->findUserByIdOrMail($input);
273
+        $email = $user->getEMailAddress();
274
+
275
+        if (empty($email)) {
276
+            throw new ResetPasswordException('Could not send reset e-mail since there is no email for username ' . $input);
277
+        }
278
+
279
+        try {
280
+            $this->limiter->registerUserRequest('lostpasswordemail', 5, 1800, $user);
281
+        } catch (RateLimitExceededException $e) {
282
+            throw new ResetPasswordException('Could not send reset e-mail, 5 of them were already sent in the last 30 minutes', 0, $e);
283
+        }
284
+
285
+        // Generate the token. It is stored encrypted in the database with the
286
+        // secret being the users' email address appended with the system secret.
287
+        // This makes the token automatically invalidate once the user changes
288
+        // their email address.
289
+        $token = $this->verificationToken->create($user, 'lostpassword', $email);
290
+
291
+        $link = $this->urlGenerator->linkToRouteAbsolute('core.lost.resetform', ['userId' => $user->getUID(), 'token' => $token]);
292
+
293
+        $emailTemplate = $this->mailer->createEMailTemplate('core.ResetPassword', [
294
+            'link' => $link,
295
+        ]);
296
+
297
+        $emailTemplate->setSubject($this->l10n->t('%s password reset', [$this->defaults->getName()]));
298
+        $emailTemplate->addHeader();
299
+        $emailTemplate->addHeading($this->l10n->t('Password reset'));
300
+
301
+        $emailTemplate->addBodyText(
302
+            htmlspecialchars($this->l10n->t('Click the following button to reset your password. If you have not requested the password reset, then ignore this email.')),
303
+            $this->l10n->t('Click the following link to reset your password. If you have not requested the password reset, then ignore this email.')
304
+        );
305
+
306
+        $emailTemplate->addBodyButton(
307
+            htmlspecialchars($this->l10n->t('Reset your password')),
308
+            $link,
309
+            false
310
+        );
311
+        $emailTemplate->addFooter();
312
+
313
+        try {
314
+            $message = $this->mailer->createMessage();
315
+            $message->setTo([$email => $user->getDisplayName()]);
316
+            $message->setFrom([$this->from => $this->defaults->getName()]);
317
+            $message->useTemplate($emailTemplate);
318
+            $this->mailer->send($message);
319
+        } catch (Exception $e) {
320
+            // Log the exception and continue
321
+            $this->logger->error($e->getMessage(), ['app' => 'core', 'exception' => $e]);
322
+        }
323
+    }
324
+
325
+    /**
326
+     * @throws ResetPasswordException
327
+     */
328
+    protected function findUserByIdOrMail(string $input): IUser {
329
+        $user = $this->userManager->get($input);
330
+        if ($user instanceof IUser) {
331
+            if (!$user->isEnabled()) {
332
+                throw new ResetPasswordException('User ' . $user->getUID() . ' is disabled');
333
+            }
334
+
335
+            return $user;
336
+        }
337
+
338
+        $users = array_filter($this->userManager->getByEmail($input), function (IUser $user) {
339
+            return $user->isEnabled();
340
+        });
341
+
342
+        if (count($users) === 1) {
343
+            return reset($users);
344
+        }
345
+
346
+        throw new ResetPasswordException('Could not find user ' . $input);
347
+    }
348 348
 }
Please login to merge, or discard this patch.
lib/public/IUserManager.php 1 patch
Indentation   +179 added lines, -179 removed lines patch added patch discarded remove patch
@@ -47,183 +47,183 @@
 block discarded – undo
47 47
  */
48 48
 interface IUserManager {
49 49
 
50
-	/**
51
-	 * @since 26.0.0
52
-	 */
53
-	public const MAX_PASSWORD_LENGTH = 469;
54
-
55
-	/**
56
-	 * register a user backend
57
-	 *
58
-	 * @param \OCP\UserInterface $backend
59
-	 * @since 8.0.0
60
-	 */
61
-	public function registerBackend($backend);
62
-
63
-	/**
64
-	 * Get the active backends
65
-	 * @return \OCP\UserInterface[]
66
-	 * @since 8.0.0
67
-	 */
68
-	public function getBackends();
69
-
70
-	/**
71
-	 * remove a user backend
72
-	 *
73
-	 * @param \OCP\UserInterface $backend
74
-	 * @since 8.0.0
75
-	 */
76
-	public function removeBackend($backend);
77
-
78
-	/**
79
-	 * remove all user backends
80
-	 * @since 8.0.0
81
-	 */
82
-	public function clearBackends() ;
83
-
84
-	/**
85
-	 * get a user by user id
86
-	 *
87
-	 * @param string $uid
88
-	 * @return \OCP\IUser|null Either the user or null if the specified user does not exist
89
-	 * @since 8.0.0
90
-	 */
91
-	public function get($uid);
92
-
93
-	/**
94
-	 * Get the display name of a user
95
-	 *
96
-	 * @param string $uid
97
-	 * @return string|null
98
-	 * @since 25.0.0
99
-	 */
100
-	public function getDisplayName(string $uid): ?string;
101
-
102
-	/**
103
-	 * check if a user exists
104
-	 *
105
-	 * @param string $uid
106
-	 * @return bool
107
-	 * @since 8.0.0
108
-	 */
109
-	public function userExists($uid);
110
-
111
-	/**
112
-	 * Check if the password is valid for the user
113
-	 *
114
-	 * @param string $loginName
115
-	 * @param string $password
116
-	 * @return IUser|false the User object on success, false otherwise
117
-	 * @since 8.0.0
118
-	 */
119
-	public function checkPassword($loginName, $password);
120
-
121
-	/**
122
-	 * search by user id
123
-	 *
124
-	 * @param string $pattern
125
-	 * @param int $limit
126
-	 * @param int $offset
127
-	 * @return \OCP\IUser[]
128
-	 * @since 8.0.0
129
-	 */
130
-	public function search($pattern, $limit = null, $offset = null);
131
-
132
-	/**
133
-	 * search by displayName
134
-	 *
135
-	 * @param string $pattern
136
-	 * @param int $limit
137
-	 * @param int $offset
138
-	 * @return \OCP\IUser[]
139
-	 * @since 8.0.0
140
-	 */
141
-	public function searchDisplayName($pattern, $limit = null, $offset = null);
142
-
143
-	/**
144
-	 * Search known users (from phonebook sync) by displayName
145
-	 *
146
-	 * @param string $searcher
147
-	 * @param string $pattern
148
-	 * @param int|null $limit
149
-	 * @param int|null $offset
150
-	 * @return IUser[]
151
-	 * @since 21.0.1
152
-	 */
153
-	public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array;
154
-
155
-	/**
156
-	 * @param string $uid
157
-	 * @param string $password
158
-	 * @throws \InvalidArgumentException
159
-	 * @return false|\OCP\IUser the created user or false
160
-	 * @since 8.0.0
161
-	 */
162
-	public function createUser($uid, $password);
163
-
164
-	/**
165
-	 * @param string $uid
166
-	 * @param string $password
167
-	 * @param UserInterface $backend
168
-	 * @return IUser|null
169
-	 * @throws \InvalidArgumentException
170
-	 * @since 12.0.0
171
-	 */
172
-	public function createUserFromBackend($uid, $password, UserInterface $backend);
173
-
174
-	/**
175
-	 * Get how many users per backend exist (if supported by backend)
176
-	 *
177
-	 * @return array<string, int> an array of backend class name as key and count number as value
178
-	 * @since 8.0.0
179
-	 */
180
-	public function countUsers();
181
-
182
-	/**
183
-	 * @param \Closure $callback
184
-	 * @psalm-param \Closure(\OCP\IUser):void $callback
185
-	 * @param string $search
186
-	 * @since 9.0.0
187
-	 */
188
-	public function callForAllUsers(\Closure $callback, $search = '');
189
-
190
-	/**
191
-	 * returns how many users have logged in once
192
-	 *
193
-	 * @return int
194
-	 * @since 11.0.0
195
-	 */
196
-	public function countDisabledUsers();
197
-
198
-	/**
199
-	 * returns how many users have logged in once
200
-	 *
201
-	 * @return int
202
-	 * @since 11.0.0
203
-	 */
204
-	public function countSeenUsers();
205
-
206
-	/**
207
-	 * @param \Closure $callback
208
-	 * @psalm-param \Closure(\OCP\IUser):?bool $callback
209
-	 * @since 11.0.0
210
-	 */
211
-	public function callForSeenUsers(\Closure $callback);
212
-
213
-	/**
214
-	 * returns all users having the provided email set as system email address
215
-	 *
216
-	 * @param string $email
217
-	 * @return IUser[]
218
-	 * @since 9.1.0
219
-	 */
220
-	public function getByEmail($email);
221
-
222
-	/**
223
-	 * @param string $uid The user ID to validate
224
-	 * @param bool $checkDataDirectory Whether it should be checked if files for the ID exist inside the data directory
225
-	 * @throws \InvalidArgumentException Message is an already translated string with a reason why the ID is not valid
226
-	 * @since 26.0.0
227
-	 */
228
-	public function validateUserId(string $uid, bool $checkDataDirectory = false): void;
50
+    /**
51
+     * @since 26.0.0
52
+     */
53
+    public const MAX_PASSWORD_LENGTH = 469;
54
+
55
+    /**
56
+     * register a user backend
57
+     *
58
+     * @param \OCP\UserInterface $backend
59
+     * @since 8.0.0
60
+     */
61
+    public function registerBackend($backend);
62
+
63
+    /**
64
+     * Get the active backends
65
+     * @return \OCP\UserInterface[]
66
+     * @since 8.0.0
67
+     */
68
+    public function getBackends();
69
+
70
+    /**
71
+     * remove a user backend
72
+     *
73
+     * @param \OCP\UserInterface $backend
74
+     * @since 8.0.0
75
+     */
76
+    public function removeBackend($backend);
77
+
78
+    /**
79
+     * remove all user backends
80
+     * @since 8.0.0
81
+     */
82
+    public function clearBackends() ;
83
+
84
+    /**
85
+     * get a user by user id
86
+     *
87
+     * @param string $uid
88
+     * @return \OCP\IUser|null Either the user or null if the specified user does not exist
89
+     * @since 8.0.0
90
+     */
91
+    public function get($uid);
92
+
93
+    /**
94
+     * Get the display name of a user
95
+     *
96
+     * @param string $uid
97
+     * @return string|null
98
+     * @since 25.0.0
99
+     */
100
+    public function getDisplayName(string $uid): ?string;
101
+
102
+    /**
103
+     * check if a user exists
104
+     *
105
+     * @param string $uid
106
+     * @return bool
107
+     * @since 8.0.0
108
+     */
109
+    public function userExists($uid);
110
+
111
+    /**
112
+     * Check if the password is valid for the user
113
+     *
114
+     * @param string $loginName
115
+     * @param string $password
116
+     * @return IUser|false the User object on success, false otherwise
117
+     * @since 8.0.0
118
+     */
119
+    public function checkPassword($loginName, $password);
120
+
121
+    /**
122
+     * search by user id
123
+     *
124
+     * @param string $pattern
125
+     * @param int $limit
126
+     * @param int $offset
127
+     * @return \OCP\IUser[]
128
+     * @since 8.0.0
129
+     */
130
+    public function search($pattern, $limit = null, $offset = null);
131
+
132
+    /**
133
+     * search by displayName
134
+     *
135
+     * @param string $pattern
136
+     * @param int $limit
137
+     * @param int $offset
138
+     * @return \OCP\IUser[]
139
+     * @since 8.0.0
140
+     */
141
+    public function searchDisplayName($pattern, $limit = null, $offset = null);
142
+
143
+    /**
144
+     * Search known users (from phonebook sync) by displayName
145
+     *
146
+     * @param string $searcher
147
+     * @param string $pattern
148
+     * @param int|null $limit
149
+     * @param int|null $offset
150
+     * @return IUser[]
151
+     * @since 21.0.1
152
+     */
153
+    public function searchKnownUsersByDisplayName(string $searcher, string $pattern, ?int $limit = null, ?int $offset = null): array;
154
+
155
+    /**
156
+     * @param string $uid
157
+     * @param string $password
158
+     * @throws \InvalidArgumentException
159
+     * @return false|\OCP\IUser the created user or false
160
+     * @since 8.0.0
161
+     */
162
+    public function createUser($uid, $password);
163
+
164
+    /**
165
+     * @param string $uid
166
+     * @param string $password
167
+     * @param UserInterface $backend
168
+     * @return IUser|null
169
+     * @throws \InvalidArgumentException
170
+     * @since 12.0.0
171
+     */
172
+    public function createUserFromBackend($uid, $password, UserInterface $backend);
173
+
174
+    /**
175
+     * Get how many users per backend exist (if supported by backend)
176
+     *
177
+     * @return array<string, int> an array of backend class name as key and count number as value
178
+     * @since 8.0.0
179
+     */
180
+    public function countUsers();
181
+
182
+    /**
183
+     * @param \Closure $callback
184
+     * @psalm-param \Closure(\OCP\IUser):void $callback
185
+     * @param string $search
186
+     * @since 9.0.0
187
+     */
188
+    public function callForAllUsers(\Closure $callback, $search = '');
189
+
190
+    /**
191
+     * returns how many users have logged in once
192
+     *
193
+     * @return int
194
+     * @since 11.0.0
195
+     */
196
+    public function countDisabledUsers();
197
+
198
+    /**
199
+     * returns how many users have logged in once
200
+     *
201
+     * @return int
202
+     * @since 11.0.0
203
+     */
204
+    public function countSeenUsers();
205
+
206
+    /**
207
+     * @param \Closure $callback
208
+     * @psalm-param \Closure(\OCP\IUser):?bool $callback
209
+     * @since 11.0.0
210
+     */
211
+    public function callForSeenUsers(\Closure $callback);
212
+
213
+    /**
214
+     * returns all users having the provided email set as system email address
215
+     *
216
+     * @param string $email
217
+     * @return IUser[]
218
+     * @since 9.1.0
219
+     */
220
+    public function getByEmail($email);
221
+
222
+    /**
223
+     * @param string $uid The user ID to validate
224
+     * @param bool $checkDataDirectory Whether it should be checked if files for the ID exist inside the data directory
225
+     * @throws \InvalidArgumentException Message is an already translated string with a reason why the ID is not valid
226
+     * @since 26.0.0
227
+     */
228
+    public function validateUserId(string $uid, bool $checkDataDirectory = false): void;
229 229
 }
Please login to merge, or discard this patch.
lib/private/Authentication/Token/PublicKeyTokenProvider.php 1 patch
Indentation   +409 added lines, -409 removed lines patch added patch discarded remove patch
@@ -45,413 +45,413 @@
 block discarded – undo
45 45
 use Psr\Log\LoggerInterface;
46 46
 
47 47
 class PublicKeyTokenProvider implements IProvider {
48
-	use TTransactional;
49
-
50
-	/** @var PublicKeyTokenMapper */
51
-	private $mapper;
52
-
53
-	/** @var ICrypto */
54
-	private $crypto;
55
-
56
-	/** @var IConfig */
57
-	private $config;
58
-
59
-	private IDBConnection $db;
60
-
61
-	/** @var LoggerInterface */
62
-	private $logger;
63
-
64
-	/** @var ITimeFactory */
65
-	private $time;
66
-
67
-	/** @var CappedMemoryCache */
68
-	private $cache;
69
-
70
-	public function __construct(PublicKeyTokenMapper $mapper,
71
-								ICrypto $crypto,
72
-								IConfig $config,
73
-								IDBConnection $db,
74
-								LoggerInterface $logger,
75
-								ITimeFactory $time) {
76
-		$this->mapper = $mapper;
77
-		$this->crypto = $crypto;
78
-		$this->config = $config;
79
-		$this->db = $db;
80
-		$this->logger = $logger;
81
-		$this->time = $time;
82
-
83
-		$this->cache = new CappedMemoryCache();
84
-	}
85
-
86
-	/**
87
-	 * {@inheritDoc}
88
-	 */
89
-	public function generateToken(string $token,
90
-								  string $uid,
91
-								  string $loginName,
92
-								  ?string $password,
93
-								  string $name,
94
-								  int $type = IToken::TEMPORARY_TOKEN,
95
-								  int $remember = IToken::DO_NOT_REMEMBER): IToken {
96
-		if (mb_strlen($name) > 128) {
97
-			$name = mb_substr($name, 0, 120) . '…';
98
-		}
99
-
100
-		$dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
101
-		$this->mapper->insert($dbToken);
102
-
103
-		// Add the token to the cache
104
-		$this->cache[$dbToken->getToken()] = $dbToken;
105
-
106
-		return $dbToken;
107
-	}
108
-
109
-	public function getToken(string $tokenId): IToken {
110
-		$tokenHash = $this->hashToken($tokenId);
111
-
112
-		if (isset($this->cache[$tokenHash])) {
113
-			if ($this->cache[$tokenHash] instanceof DoesNotExistException) {
114
-				$ex = $this->cache[$tokenHash];
115
-				throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
116
-			}
117
-			$token = $this->cache[$tokenHash];
118
-		} else {
119
-			try {
120
-				$token = $this->mapper->getToken($this->hashToken($tokenId));
121
-				$this->cache[$token->getToken()] = $token;
122
-			} catch (DoesNotExistException $ex) {
123
-				try {
124
-					$token = $this->mapper->getToken($this->hashTokenWithEmptySecret($tokenId));
125
-					$this->cache[$token->getToken()] = $token;
126
-					$this->rotate($token, $tokenId, $tokenId);
127
-				} catch (DoesNotExistException $ex2) {
128
-					$this->cache[$tokenHash] = $ex2;
129
-					throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
130
-				}
131
-			}
132
-		}
133
-
134
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
135
-			throw new ExpiredTokenException($token);
136
-		}
137
-
138
-		if ($token->getType() === IToken::WIPE_TOKEN) {
139
-			throw new WipeTokenException($token);
140
-		}
141
-
142
-		if ($token->getPasswordInvalid() === true) {
143
-			//The password is invalid we should throw an TokenPasswordExpiredException
144
-			throw new TokenPasswordExpiredException($token);
145
-		}
146
-
147
-		return $token;
148
-	}
149
-
150
-	public function getTokenById(int $tokenId): IToken {
151
-		try {
152
-			$token = $this->mapper->getTokenById($tokenId);
153
-		} catch (DoesNotExistException $ex) {
154
-			throw new InvalidTokenException("Token with ID $tokenId does not exist: " . $ex->getMessage(), 0, $ex);
155
-		}
156
-
157
-		if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
158
-			throw new ExpiredTokenException($token);
159
-		}
160
-
161
-		if ($token->getType() === IToken::WIPE_TOKEN) {
162
-			throw new WipeTokenException($token);
163
-		}
164
-
165
-		if ($token->getPasswordInvalid() === true) {
166
-			//The password is invalid we should throw an TokenPasswordExpiredException
167
-			throw new TokenPasswordExpiredException($token);
168
-		}
169
-
170
-		return $token;
171
-	}
172
-
173
-	public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
174
-		$this->cache->clear();
175
-
176
-		return $this->atomic(function () use ($oldSessionId, $sessionId) {
177
-			$token = $this->getToken($oldSessionId);
178
-
179
-			if (!($token instanceof PublicKeyToken)) {
180
-				throw new InvalidTokenException("Invalid token type");
181
-			}
182
-
183
-			$password = null;
184
-			if (!is_null($token->getPassword())) {
185
-				$privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
186
-				$password = $this->decryptPassword($token->getPassword(), $privateKey);
187
-			}
188
-			$newToken = $this->generateToken(
189
-				$sessionId,
190
-				$token->getUID(),
191
-				$token->getLoginName(),
192
-				$password,
193
-				$token->getName(),
194
-				IToken::TEMPORARY_TOKEN,
195
-				$token->getRemember()
196
-			);
197
-
198
-			$this->mapper->delete($token);
199
-
200
-			return $newToken;
201
-		}, $this->db);
202
-	}
203
-
204
-	public function invalidateToken(string $token) {
205
-		$this->cache->clear();
206
-
207
-		$this->mapper->invalidate($this->hashToken($token));
208
-		$this->mapper->invalidate($this->hashTokenWithEmptySecret($token));
209
-	}
210
-
211
-	public function invalidateTokenById(string $uid, int $id) {
212
-		$this->cache->clear();
213
-
214
-		$this->mapper->deleteById($uid, $id);
215
-	}
216
-
217
-	public function invalidateOldTokens() {
218
-		$this->cache->clear();
219
-
220
-		$olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
221
-		$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
222
-		$this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
223
-		$rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
224
-		$this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
225
-		$this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
226
-	}
227
-
228
-	public function updateToken(IToken $token) {
229
-		$this->cache->clear();
230
-
231
-		if (!($token instanceof PublicKeyToken)) {
232
-			throw new InvalidTokenException("Invalid token type");
233
-		}
234
-		$this->mapper->update($token);
235
-	}
236
-
237
-	public function updateTokenActivity(IToken $token) {
238
-		$this->cache->clear();
239
-
240
-		if (!($token instanceof PublicKeyToken)) {
241
-			throw new InvalidTokenException("Invalid token type");
242
-		}
243
-
244
-		$activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60);
245
-		$activityInterval = min(max($activityInterval, 0), 300);
246
-
247
-		/** @var PublicKeyToken $token */
248
-		$now = $this->time->getTime();
249
-		if ($token->getLastActivity() < ($now - $activityInterval)) {
250
-			$token->setLastActivity($now);
251
-			$this->mapper->updateActivity($token, $now);
252
-		}
253
-	}
254
-
255
-	public function getTokenByUser(string $uid): array {
256
-		return $this->mapper->getTokenByUser($uid);
257
-	}
258
-
259
-	public function getPassword(IToken $savedToken, string $tokenId): string {
260
-		if (!($savedToken instanceof PublicKeyToken)) {
261
-			throw new InvalidTokenException("Invalid token type");
262
-		}
263
-
264
-		if ($savedToken->getPassword() === null) {
265
-			throw new PasswordlessTokenException();
266
-		}
267
-
268
-		// Decrypt private key with tokenId
269
-		$privateKey = $this->decrypt($savedToken->getPrivateKey(), $tokenId);
270
-
271
-		// Decrypt password with private key
272
-		return $this->decryptPassword($savedToken->getPassword(), $privateKey);
273
-	}
274
-
275
-	public function setPassword(IToken $token, string $tokenId, string $password) {
276
-		$this->cache->clear();
277
-
278
-		if (!($token instanceof PublicKeyToken)) {
279
-			throw new InvalidTokenException("Invalid token type");
280
-		}
281
-
282
-		// When changing passwords all temp tokens are deleted
283
-		$this->mapper->deleteTempToken($token);
284
-
285
-		// Update the password for all tokens
286
-		$tokens = $this->mapper->getTokenByUser($token->getUID());
287
-		foreach ($tokens as $t) {
288
-			$publicKey = $t->getPublicKey();
289
-			$t->setPassword($this->encryptPassword($password, $publicKey));
290
-			$this->updateToken($t);
291
-		}
292
-	}
293
-
294
-	public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
295
-		$this->cache->clear();
296
-
297
-		if (!($token instanceof PublicKeyToken)) {
298
-			throw new InvalidTokenException("Invalid token type");
299
-		}
300
-
301
-		// Decrypt private key with oldTokenId
302
-		$privateKey = $this->decrypt($token->getPrivateKey(), $oldTokenId);
303
-		// Encrypt with the new token
304
-		$token->setPrivateKey($this->encrypt($privateKey, $newTokenId));
305
-
306
-		$token->setToken($this->hashToken($newTokenId));
307
-		$this->updateToken($token);
308
-
309
-		return $token;
310
-	}
311
-
312
-	private function encrypt(string $plaintext, string $token): string {
313
-		$secret = $this->config->getSystemValue('secret');
314
-		return $this->crypto->encrypt($plaintext, $token . $secret);
315
-	}
316
-
317
-	/**
318
-	 * @throws InvalidTokenException
319
-	 */
320
-	private function decrypt(string $cipherText, string $token): string {
321
-		$secret = $this->config->getSystemValue('secret');
322
-		try {
323
-			return $this->crypto->decrypt($cipherText, $token . $secret);
324
-		} catch (\Exception $ex) {
325
-			// Retry with empty secret as a fallback for instances where the secret might not have been set by accident
326
-			try {
327
-				return $this->crypto->decrypt($cipherText, $token);
328
-			} catch (\Exception $ex2) {
329
-				// Delete the invalid token
330
-				$this->invalidateToken($token);
331
-				throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex2);
332
-			}
333
-		}
334
-	}
335
-
336
-	private function encryptPassword(string $password, string $publicKey): string {
337
-		openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
338
-		$encryptedPassword = base64_encode($encryptedPassword);
339
-
340
-		return $encryptedPassword;
341
-	}
342
-
343
-	private function decryptPassword(string $encryptedPassword, string $privateKey): string {
344
-		$encryptedPassword = base64_decode($encryptedPassword);
345
-		openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
346
-
347
-		return $password;
348
-	}
349
-
350
-	private function hashToken(string $token): string {
351
-		$secret = $this->config->getSystemValue('secret');
352
-		return hash('sha512', $token . $secret);
353
-	}
354
-
355
-	/**
356
-	 * @deprecated Fallback for instances where the secret might not have been set by accident
357
-	 */
358
-	private function hashTokenWithEmptySecret(string $token): string {
359
-		return hash('sha512', $token);
360
-	}
361
-
362
-	/**
363
-	 * @throws \RuntimeException when OpenSSL reports a problem
364
-	 */
365
-	private function newToken(string $token,
366
-							  string $uid,
367
-							  string $loginName,
368
-							  $password,
369
-							  string $name,
370
-							  int $type,
371
-							  int $remember): PublicKeyToken {
372
-		$dbToken = new PublicKeyToken();
373
-		$dbToken->setUid($uid);
374
-		$dbToken->setLoginName($loginName);
375
-
376
-		$config = array_merge([
377
-			'digest_alg' => 'sha512',
378
-			'private_key_bits' => $password !== null && strlen($password) > 250 ? 4096 : 2048,
379
-		], $this->config->getSystemValue('openssl', []));
380
-
381
-		// Generate new key
382
-		$res = openssl_pkey_new($config);
383
-		if ($res === false) {
384
-			$this->logOpensslError();
385
-			throw new \RuntimeException('OpenSSL reported a problem');
386
-		}
387
-
388
-		if (openssl_pkey_export($res, $privateKey, null, $config) === false) {
389
-			$this->logOpensslError();
390
-			throw new \RuntimeException('OpenSSL reported a problem');
391
-		}
392
-
393
-		// Extract the public key from $res to $pubKey
394
-		$publicKey = openssl_pkey_get_details($res);
395
-		$publicKey = $publicKey['key'];
396
-
397
-		$dbToken->setPublicKey($publicKey);
398
-		$dbToken->setPrivateKey($this->encrypt($privateKey, $token));
399
-
400
-		if (!is_null($password) && $this->config->getSystemValueBool('auth.storeCryptedPassword', true)) {
401
-			if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
402
-				throw new \RuntimeException('Trying to save a password with more than 469 characters is not supported. If you want to use big passwords, disable the auth.storeCryptedPassword option in config.php');
403
-			}
404
-			$dbToken->setPassword($this->encryptPassword($password, $publicKey));
405
-		}
406
-
407
-		$dbToken->setName($name);
408
-		$dbToken->setToken($this->hashToken($token));
409
-		$dbToken->setType($type);
410
-		$dbToken->setRemember($remember);
411
-		$dbToken->setLastActivity($this->time->getTime());
412
-		$dbToken->setLastCheck($this->time->getTime());
413
-		$dbToken->setVersion(PublicKeyToken::VERSION);
414
-
415
-		return $dbToken;
416
-	}
417
-
418
-	public function markPasswordInvalid(IToken $token, string $tokenId) {
419
-		$this->cache->clear();
420
-
421
-		if (!($token instanceof PublicKeyToken)) {
422
-			throw new InvalidTokenException("Invalid token type");
423
-		}
424
-
425
-		$token->setPasswordInvalid(true);
426
-		$this->mapper->update($token);
427
-	}
428
-
429
-	public function updatePasswords(string $uid, string $password) {
430
-		$this->cache->clear();
431
-
432
-		// prevent setting an empty pw as result of pw-less-login
433
-		if ($password === '' || !$this->config->getSystemValueBool('auth.storeCryptedPassword', true)) {
434
-			return;
435
-		}
436
-
437
-		// Update the password for all tokens
438
-		$tokens = $this->mapper->getTokenByUser($uid);
439
-		foreach ($tokens as $t) {
440
-			$publicKey = $t->getPublicKey();
441
-			$encryptedPassword = $this->encryptPassword($password, $publicKey);
442
-			if ($t->getPassword() !== $encryptedPassword) {
443
-				$t->setPassword($encryptedPassword);
444
-				$t->setPasswordInvalid(false);
445
-				$this->updateToken($t);
446
-			}
447
-		}
448
-	}
449
-
450
-	private function logOpensslError() {
451
-		$errors = [];
452
-		while ($error = openssl_error_string()) {
453
-			$errors[] = $error;
454
-		}
455
-		$this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
456
-	}
48
+    use TTransactional;
49
+
50
+    /** @var PublicKeyTokenMapper */
51
+    private $mapper;
52
+
53
+    /** @var ICrypto */
54
+    private $crypto;
55
+
56
+    /** @var IConfig */
57
+    private $config;
58
+
59
+    private IDBConnection $db;
60
+
61
+    /** @var LoggerInterface */
62
+    private $logger;
63
+
64
+    /** @var ITimeFactory */
65
+    private $time;
66
+
67
+    /** @var CappedMemoryCache */
68
+    private $cache;
69
+
70
+    public function __construct(PublicKeyTokenMapper $mapper,
71
+                                ICrypto $crypto,
72
+                                IConfig $config,
73
+                                IDBConnection $db,
74
+                                LoggerInterface $logger,
75
+                                ITimeFactory $time) {
76
+        $this->mapper = $mapper;
77
+        $this->crypto = $crypto;
78
+        $this->config = $config;
79
+        $this->db = $db;
80
+        $this->logger = $logger;
81
+        $this->time = $time;
82
+
83
+        $this->cache = new CappedMemoryCache();
84
+    }
85
+
86
+    /**
87
+     * {@inheritDoc}
88
+     */
89
+    public function generateToken(string $token,
90
+                                    string $uid,
91
+                                    string $loginName,
92
+                                  ?string $password,
93
+                                    string $name,
94
+                                    int $type = IToken::TEMPORARY_TOKEN,
95
+                                    int $remember = IToken::DO_NOT_REMEMBER): IToken {
96
+        if (mb_strlen($name) > 128) {
97
+            $name = mb_substr($name, 0, 120) . '…';
98
+        }
99
+
100
+        $dbToken = $this->newToken($token, $uid, $loginName, $password, $name, $type, $remember);
101
+        $this->mapper->insert($dbToken);
102
+
103
+        // Add the token to the cache
104
+        $this->cache[$dbToken->getToken()] = $dbToken;
105
+
106
+        return $dbToken;
107
+    }
108
+
109
+    public function getToken(string $tokenId): IToken {
110
+        $tokenHash = $this->hashToken($tokenId);
111
+
112
+        if (isset($this->cache[$tokenHash])) {
113
+            if ($this->cache[$tokenHash] instanceof DoesNotExistException) {
114
+                $ex = $this->cache[$tokenHash];
115
+                throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
116
+            }
117
+            $token = $this->cache[$tokenHash];
118
+        } else {
119
+            try {
120
+                $token = $this->mapper->getToken($this->hashToken($tokenId));
121
+                $this->cache[$token->getToken()] = $token;
122
+            } catch (DoesNotExistException $ex) {
123
+                try {
124
+                    $token = $this->mapper->getToken($this->hashTokenWithEmptySecret($tokenId));
125
+                    $this->cache[$token->getToken()] = $token;
126
+                    $this->rotate($token, $tokenId, $tokenId);
127
+                } catch (DoesNotExistException $ex2) {
128
+                    $this->cache[$tokenHash] = $ex2;
129
+                    throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
130
+                }
131
+            }
132
+        }
133
+
134
+        if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
135
+            throw new ExpiredTokenException($token);
136
+        }
137
+
138
+        if ($token->getType() === IToken::WIPE_TOKEN) {
139
+            throw new WipeTokenException($token);
140
+        }
141
+
142
+        if ($token->getPasswordInvalid() === true) {
143
+            //The password is invalid we should throw an TokenPasswordExpiredException
144
+            throw new TokenPasswordExpiredException($token);
145
+        }
146
+
147
+        return $token;
148
+    }
149
+
150
+    public function getTokenById(int $tokenId): IToken {
151
+        try {
152
+            $token = $this->mapper->getTokenById($tokenId);
153
+        } catch (DoesNotExistException $ex) {
154
+            throw new InvalidTokenException("Token with ID $tokenId does not exist: " . $ex->getMessage(), 0, $ex);
155
+        }
156
+
157
+        if ((int)$token->getExpires() !== 0 && $token->getExpires() < $this->time->getTime()) {
158
+            throw new ExpiredTokenException($token);
159
+        }
160
+
161
+        if ($token->getType() === IToken::WIPE_TOKEN) {
162
+            throw new WipeTokenException($token);
163
+        }
164
+
165
+        if ($token->getPasswordInvalid() === true) {
166
+            //The password is invalid we should throw an TokenPasswordExpiredException
167
+            throw new TokenPasswordExpiredException($token);
168
+        }
169
+
170
+        return $token;
171
+    }
172
+
173
+    public function renewSessionToken(string $oldSessionId, string $sessionId): IToken {
174
+        $this->cache->clear();
175
+
176
+        return $this->atomic(function () use ($oldSessionId, $sessionId) {
177
+            $token = $this->getToken($oldSessionId);
178
+
179
+            if (!($token instanceof PublicKeyToken)) {
180
+                throw new InvalidTokenException("Invalid token type");
181
+            }
182
+
183
+            $password = null;
184
+            if (!is_null($token->getPassword())) {
185
+                $privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
186
+                $password = $this->decryptPassword($token->getPassword(), $privateKey);
187
+            }
188
+            $newToken = $this->generateToken(
189
+                $sessionId,
190
+                $token->getUID(),
191
+                $token->getLoginName(),
192
+                $password,
193
+                $token->getName(),
194
+                IToken::TEMPORARY_TOKEN,
195
+                $token->getRemember()
196
+            );
197
+
198
+            $this->mapper->delete($token);
199
+
200
+            return $newToken;
201
+        }, $this->db);
202
+    }
203
+
204
+    public function invalidateToken(string $token) {
205
+        $this->cache->clear();
206
+
207
+        $this->mapper->invalidate($this->hashToken($token));
208
+        $this->mapper->invalidate($this->hashTokenWithEmptySecret($token));
209
+    }
210
+
211
+    public function invalidateTokenById(string $uid, int $id) {
212
+        $this->cache->clear();
213
+
214
+        $this->mapper->deleteById($uid, $id);
215
+    }
216
+
217
+    public function invalidateOldTokens() {
218
+        $this->cache->clear();
219
+
220
+        $olderThan = $this->time->getTime() - (int) $this->config->getSystemValue('session_lifetime', 60 * 60 * 24);
221
+        $this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
222
+        $this->mapper->invalidateOld($olderThan, IToken::DO_NOT_REMEMBER);
223
+        $rememberThreshold = $this->time->getTime() - (int) $this->config->getSystemValue('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
224
+        $this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
225
+        $this->mapper->invalidateOld($rememberThreshold, IToken::REMEMBER);
226
+    }
227
+
228
+    public function updateToken(IToken $token) {
229
+        $this->cache->clear();
230
+
231
+        if (!($token instanceof PublicKeyToken)) {
232
+            throw new InvalidTokenException("Invalid token type");
233
+        }
234
+        $this->mapper->update($token);
235
+    }
236
+
237
+    public function updateTokenActivity(IToken $token) {
238
+        $this->cache->clear();
239
+
240
+        if (!($token instanceof PublicKeyToken)) {
241
+            throw new InvalidTokenException("Invalid token type");
242
+        }
243
+
244
+        $activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60);
245
+        $activityInterval = min(max($activityInterval, 0), 300);
246
+
247
+        /** @var PublicKeyToken $token */
248
+        $now = $this->time->getTime();
249
+        if ($token->getLastActivity() < ($now - $activityInterval)) {
250
+            $token->setLastActivity($now);
251
+            $this->mapper->updateActivity($token, $now);
252
+        }
253
+    }
254
+
255
+    public function getTokenByUser(string $uid): array {
256
+        return $this->mapper->getTokenByUser($uid);
257
+    }
258
+
259
+    public function getPassword(IToken $savedToken, string $tokenId): string {
260
+        if (!($savedToken instanceof PublicKeyToken)) {
261
+            throw new InvalidTokenException("Invalid token type");
262
+        }
263
+
264
+        if ($savedToken->getPassword() === null) {
265
+            throw new PasswordlessTokenException();
266
+        }
267
+
268
+        // Decrypt private key with tokenId
269
+        $privateKey = $this->decrypt($savedToken->getPrivateKey(), $tokenId);
270
+
271
+        // Decrypt password with private key
272
+        return $this->decryptPassword($savedToken->getPassword(), $privateKey);
273
+    }
274
+
275
+    public function setPassword(IToken $token, string $tokenId, string $password) {
276
+        $this->cache->clear();
277
+
278
+        if (!($token instanceof PublicKeyToken)) {
279
+            throw new InvalidTokenException("Invalid token type");
280
+        }
281
+
282
+        // When changing passwords all temp tokens are deleted
283
+        $this->mapper->deleteTempToken($token);
284
+
285
+        // Update the password for all tokens
286
+        $tokens = $this->mapper->getTokenByUser($token->getUID());
287
+        foreach ($tokens as $t) {
288
+            $publicKey = $t->getPublicKey();
289
+            $t->setPassword($this->encryptPassword($password, $publicKey));
290
+            $this->updateToken($t);
291
+        }
292
+    }
293
+
294
+    public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken {
295
+        $this->cache->clear();
296
+
297
+        if (!($token instanceof PublicKeyToken)) {
298
+            throw new InvalidTokenException("Invalid token type");
299
+        }
300
+
301
+        // Decrypt private key with oldTokenId
302
+        $privateKey = $this->decrypt($token->getPrivateKey(), $oldTokenId);
303
+        // Encrypt with the new token
304
+        $token->setPrivateKey($this->encrypt($privateKey, $newTokenId));
305
+
306
+        $token->setToken($this->hashToken($newTokenId));
307
+        $this->updateToken($token);
308
+
309
+        return $token;
310
+    }
311
+
312
+    private function encrypt(string $plaintext, string $token): string {
313
+        $secret = $this->config->getSystemValue('secret');
314
+        return $this->crypto->encrypt($plaintext, $token . $secret);
315
+    }
316
+
317
+    /**
318
+     * @throws InvalidTokenException
319
+     */
320
+    private function decrypt(string $cipherText, string $token): string {
321
+        $secret = $this->config->getSystemValue('secret');
322
+        try {
323
+            return $this->crypto->decrypt($cipherText, $token . $secret);
324
+        } catch (\Exception $ex) {
325
+            // Retry with empty secret as a fallback for instances where the secret might not have been set by accident
326
+            try {
327
+                return $this->crypto->decrypt($cipherText, $token);
328
+            } catch (\Exception $ex2) {
329
+                // Delete the invalid token
330
+                $this->invalidateToken($token);
331
+                throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex2);
332
+            }
333
+        }
334
+    }
335
+
336
+    private function encryptPassword(string $password, string $publicKey): string {
337
+        openssl_public_encrypt($password, $encryptedPassword, $publicKey, OPENSSL_PKCS1_OAEP_PADDING);
338
+        $encryptedPassword = base64_encode($encryptedPassword);
339
+
340
+        return $encryptedPassword;
341
+    }
342
+
343
+    private function decryptPassword(string $encryptedPassword, string $privateKey): string {
344
+        $encryptedPassword = base64_decode($encryptedPassword);
345
+        openssl_private_decrypt($encryptedPassword, $password, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
346
+
347
+        return $password;
348
+    }
349
+
350
+    private function hashToken(string $token): string {
351
+        $secret = $this->config->getSystemValue('secret');
352
+        return hash('sha512', $token . $secret);
353
+    }
354
+
355
+    /**
356
+     * @deprecated Fallback for instances where the secret might not have been set by accident
357
+     */
358
+    private function hashTokenWithEmptySecret(string $token): string {
359
+        return hash('sha512', $token);
360
+    }
361
+
362
+    /**
363
+     * @throws \RuntimeException when OpenSSL reports a problem
364
+     */
365
+    private function newToken(string $token,
366
+                                string $uid,
367
+                                string $loginName,
368
+                                $password,
369
+                                string $name,
370
+                                int $type,
371
+                                int $remember): PublicKeyToken {
372
+        $dbToken = new PublicKeyToken();
373
+        $dbToken->setUid($uid);
374
+        $dbToken->setLoginName($loginName);
375
+
376
+        $config = array_merge([
377
+            'digest_alg' => 'sha512',
378
+            'private_key_bits' => $password !== null && strlen($password) > 250 ? 4096 : 2048,
379
+        ], $this->config->getSystemValue('openssl', []));
380
+
381
+        // Generate new key
382
+        $res = openssl_pkey_new($config);
383
+        if ($res === false) {
384
+            $this->logOpensslError();
385
+            throw new \RuntimeException('OpenSSL reported a problem');
386
+        }
387
+
388
+        if (openssl_pkey_export($res, $privateKey, null, $config) === false) {
389
+            $this->logOpensslError();
390
+            throw new \RuntimeException('OpenSSL reported a problem');
391
+        }
392
+
393
+        // Extract the public key from $res to $pubKey
394
+        $publicKey = openssl_pkey_get_details($res);
395
+        $publicKey = $publicKey['key'];
396
+
397
+        $dbToken->setPublicKey($publicKey);
398
+        $dbToken->setPrivateKey($this->encrypt($privateKey, $token));
399
+
400
+        if (!is_null($password) && $this->config->getSystemValueBool('auth.storeCryptedPassword', true)) {
401
+            if (strlen($password) > IUserManager::MAX_PASSWORD_LENGTH) {
402
+                throw new \RuntimeException('Trying to save a password with more than 469 characters is not supported. If you want to use big passwords, disable the auth.storeCryptedPassword option in config.php');
403
+            }
404
+            $dbToken->setPassword($this->encryptPassword($password, $publicKey));
405
+        }
406
+
407
+        $dbToken->setName($name);
408
+        $dbToken->setToken($this->hashToken($token));
409
+        $dbToken->setType($type);
410
+        $dbToken->setRemember($remember);
411
+        $dbToken->setLastActivity($this->time->getTime());
412
+        $dbToken->setLastCheck($this->time->getTime());
413
+        $dbToken->setVersion(PublicKeyToken::VERSION);
414
+
415
+        return $dbToken;
416
+    }
417
+
418
+    public function markPasswordInvalid(IToken $token, string $tokenId) {
419
+        $this->cache->clear();
420
+
421
+        if (!($token instanceof PublicKeyToken)) {
422
+            throw new InvalidTokenException("Invalid token type");
423
+        }
424
+
425
+        $token->setPasswordInvalid(true);
426
+        $this->mapper->update($token);
427
+    }
428
+
429
+    public function updatePasswords(string $uid, string $password) {
430
+        $this->cache->clear();
431
+
432
+        // prevent setting an empty pw as result of pw-less-login
433
+        if ($password === '' || !$this->config->getSystemValueBool('auth.storeCryptedPassword', true)) {
434
+            return;
435
+        }
436
+
437
+        // Update the password for all tokens
438
+        $tokens = $this->mapper->getTokenByUser($uid);
439
+        foreach ($tokens as $t) {
440
+            $publicKey = $t->getPublicKey();
441
+            $encryptedPassword = $this->encryptPassword($password, $publicKey);
442
+            if ($t->getPassword() !== $encryptedPassword) {
443
+                $t->setPassword($encryptedPassword);
444
+                $t->setPasswordInvalid(false);
445
+                $this->updateToken($t);
446
+            }
447
+        }
448
+    }
449
+
450
+    private function logOpensslError() {
451
+        $errors = [];
452
+        while ($error = openssl_error_string()) {
453
+            $errors[] = $error;
454
+        }
455
+        $this->logger->critical('Something is wrong with your openssl setup: ' . implode(', ', $errors));
456
+    }
457 457
 }
Please login to merge, or discard this patch.