4949use Throwable ;
5050
5151class Backend extends ABackend implements IPasswordConfirmationBackend, IGetDisplayNameBackend, IApacheBackend, ICustomLogout, ICountUsersBackend {
52+ private const SESSION_USER_DATA = 'user_oidc.oidcUserData ' ;
53+
54+ /** @var list<class-string<IBearerTokenValidator>> */
5255 private $ tokenValidators = [
5356 SelfEncodedValidator::class,
5457 UserInfoValidator::class,
@@ -178,14 +181,23 @@ public function getLogoutUrl(): string {
178181 * Inspired by user_saml
179182 */
180183 public function getUserData (): array {
181- $ userData = $ this ->session ->get ('user_oidc.oidcUserData ' );
182- $ providerId = (int )$ this ->session ->get (LoginController::PROVIDERID );
183- $ userData = $ this ->formatUserData ($ providerId , $ userData );
184-
185- // make sure that a valid UID is given
186- if (empty ($ userData ['formatted ' ]['uid ' ])) {
187- $ this ->logger ->error ('No valid uid given, please check your attribute mapping. Got uid: {uid} ' , ['app ' => 'user_oidc ' , 'uid ' => $ userData ['formatted ' ]['uid ' ]]);
188- throw new \InvalidArgumentException ('No valid uid given, please check your attribute mapping. Got uid: ' . $ userData ['formatted ' ]['uid ' ]);
184+ $ userData = $ this ->session ->get (self ::SESSION_USER_DATA ) ?? [];
185+ $ rawProviderId = $ this ->session ->get (LoginController::PROVIDERID );
186+ if ($ rawProviderId === null ) {
187+ throw new \InvalidArgumentException ('No OIDC provider ID in session ' );
188+ }
189+
190+ $ providerId = (int )$ rawProviderId ;
191+
192+ $ userData = $ this ->formatUserData ($ providerId , is_array ($ userData ) ? $ userData : []);
193+
194+ if (!$ this ->isAcceptableUserId ($ userData ['formatted ' ]['uid ' ] ?? null )) {
195+ $ uid = is_scalar ($ userData ['formatted ' ]['uid ' ] ?? null ) ? (string )$ userData ['formatted ' ]['uid ' ] : '' ;
196+ $ this ->logger ->error ('No valid uid given, please check your attribute mapping. Got uid: {uid} ' , [
197+ 'app ' => 'user_oidc ' ,
198+ 'uid ' => $ uid ,
199+ ]);
200+ throw new \InvalidArgumentException ('No valid uid given, please check your attribute mapping. Got uid: ' . $ uid );
189201 }
190202
191203 return $ userData ;
@@ -388,6 +400,15 @@ public function getCurrentUserId(): string {
388400 return '' ;
389401 }
390402
403+ /**
404+ * Returns true only if $userId is a non-empty, non-whitespace-only string.
405+ * Used as a lightweight sanity check on user IDs returned by token validators
406+ * before any database lookup or provisioning takes place.
407+ */
408+ private function isAcceptableUserId (mixed $ userId ): bool {
409+ return is_string ($ userId ) && $ userId !== '' && trim ($ userId ) !== '' ;
410+ }
411+
391412 /**
392413 * Set the user in IUserSession after bearer token validation.
393414 * Without this, DI-injected $userId is null in OCS controllers
@@ -414,55 +435,65 @@ private function setSessionUser(string $userId): void {
414435 }
415436
416437 /**
417- * Inspired by lib/private/User/Session.php::prepareUserLogin()
418438 *
419- * @param string $userId
420- * @return bool
421- * @throws NotFoundException
439+ * Performs first-login initialisation (home folder setup, skeleton copy, events)
440+ * if the user has never logged in before, then updates the last-login timestamp.
441+ * Inspired by lib/private/User/Session.php::prepareUserLogin().
422442 */
423443 private function checkFirstLogin (string $ userId ): bool {
424444 $ user = $ this ->userManager ->get ($ userId );
425-
426445 if ($ user === null ) {
427446 return false ;
428447 }
429448
430449 $ firstLogin = $ user ->getLastLogin () === 0 ;
431450 if ($ firstLogin ) {
432- /** @psalm-suppress UndefinedVariable Replace with ServerVersion once we depends on NC 31 */
433- $ OC_Version [0 ] >= 34 ? Server::get (ISetupManager::class)->setupForUser ($ user ) : \OC_Util::setupFS ($ userId );
434- // trigger creation of user home and /files folder
435- $ userFolder = Server::get (IRootFolder::class)->getUserFolder ($ userId );
451+ /** @psalm-suppress UndefinedVariable Replace with ServerVersion once we depend on NC 31 */
452+ if ($ OC_Version [0 ] >= 34 ) {
453+ Server::get (ISetupManager::class)->setupForUser ($ user );
454+ } else {
455+ \OC_Util::setupFS ($ userId );
456+ }
457+
436458 try {
459+ // trigger creation of user home and /files folder
460+ $ userFolder = Server::get (IRootFolder::class)->getUserFolder ($ userId );
437461 // copy skeleton
438462 \OC_Util::copySkeleton ($ userId , $ userFolder );
439- } catch (NotPermittedException $ ex ) {
440- // read only uses
463+ } catch (NotFoundException | NotPermittedException $ ex ) {
464+ $ this -> logger -> warning ( ' Could not set up user folder on first login ' , [ ' exception ' => $ ex ]);
441465 }
442466
443- // trigger any other initialization
444467 $ this ->eventDispatcher ->dispatch (IUser::class . '::firstLogin ' , new GenericEvent ($ user ));
445468 $ this ->eventDispatcher ->dispatchTyped (new UserFirstTimeLoggedInEvent ($ user ));
446469 }
470+
447471 $ user ->updateLastLoginTimestamp ();
448472 return $ firstLogin ;
449473 }
450474
451475 /**
452476 * Triggers user provisioning based on the provided strategy
453- *
454- * @param string $provisioningStrategyClass
455- * @param Provider $provider
456- * @param string $tokenUserId
457- * @param string $headerToken
458- * @param IUser|null $existingUser
459- * @return IUser|null
460477 */
461478 private function provisionUser (
462- string $ provisioningStrategyClass , Provider $ provider , string $ tokenUserId , string $ headerToken ,
479+ string $ provisioningStrategyClass ,
480+ Provider $ provider ,
481+ string $ tokenUserId ,
482+ string $ headerToken ,
463483 ?IUser $ existingUser ,
464484 ): ?IUser {
465- $ provisioningStrategy = Server::get ($ provisioningStrategyClass );
466- return $ provisioningStrategy ->provisionUser ($ provider , $ tokenUserId , $ headerToken , $ existingUser );
485+ try {
486+ $ provisioningStrategy = Server::get ($ provisioningStrategyClass );
487+ return $ provisioningStrategy ->provisionUser ($ provider , $ tokenUserId , $ headerToken , $ existingUser );
488+ } catch (Throwable $ e ) {
489+ $ this ->logger ->error ('Failed to provision user via strategy ' , [
490+ 'strategy ' => $ provisioningStrategyClass ,
491+ 'providerId ' => $ provider ->getId (),
492+ 'providerIdentifier ' => $ provider ->getIdentifier (),
493+ 'userId ' => $ tokenUserId ,
494+ 'exception ' => $ e ,
495+ ]);
496+ return null ;
497+ }
467498 }
468499}
0 commit comments