@@ -35,6 +35,48 @@ class UserController extends Controller
3535 public $ doNotSanitize = [
3636 'username ' , // has alpha_dash rule
3737 'password ' ,
38+ 'firstname ' , // validated as plain text by User::rules()
39+ 'lastname ' , // validated as plain text by User::rules()
40+ 'title ' , // validated as plain text by User::rules()
41+ ];
42+
43+ /**
44+ * Fields accepted when a non-administrative user updates their own profile.
45+ *
46+ * @var array<string>
47+ */
48+ private const SELF_SERVICE_UPDATE_FIELDS = [
49+ 'username ' ,
50+ 'password ' ,
51+ 'firstname ' ,
52+ 'lastname ' ,
53+ 'title ' ,
54+ 'email ' ,
55+ 'address ' ,
56+ 'city ' ,
57+ 'state ' ,
58+ 'postal ' ,
59+ 'country ' ,
60+ 'phone ' ,
61+ 'fax ' ,
62+ 'cell ' ,
63+ 'timezone ' ,
64+ 'datetime_format ' ,
65+ 'status ' ,
66+ 'avatar ' ,
67+ 'preferences_2fa ' ,
68+ 'connected_accounts ' ,
69+ 'meta ' ,
70+ 'valpassword ' ,
71+ ];
72+
73+ /**
74+ * Metadata fields accepted during a self-service profile update.
75+ *
76+ * @var array<string>
77+ */
78+ private const SELF_SERVICE_META_FIELDS = [
79+ 'disableRecommendations ' ,
3880 ];
3981
4082 /**
@@ -452,12 +494,22 @@ public function getPinnnedControls(User $user)
452494 */
453495 public function update (User $ user , Request $ request )
454496 {
455- if (!Auth::user ()->can ('edit ' , $ user )) {
497+ $ authenticatedUser = Auth::user ();
498+ if (!$ authenticatedUser ->can ('edit ' , $ user )) {
456499 throw new AuthorizationException (__ ('Not authorized to update this user. ' ));
457500 }
458501
459- $ request ->validate (User::rules ($ user ));
460502 $ fields = $ request ->json ()->all ();
503+ $ isSelfServiceUpdate = $ this ->authorizeSelfServiceUpdate ($ authenticatedUser , $ user , $ fields );
504+ $ rules = User::rules ($ user );
505+ if ($ isSelfServiceUpdate ) {
506+ $ rules ['meta ' ] = ['sometimes ' , 'array ' ];
507+ $ rules ['meta.disableRecommendations ' ] = ['sometimes ' , 'boolean ' ];
508+ }
509+ $ request ->validate ($ rules );
510+ if ($ isSelfServiceUpdate ) {
511+ $ fields = $ this ->normalizeSelfServiceMeta ($ user , $ fields );
512+ }
461513 if (isset ($ fields ['password ' ])) {
462514 $ fields ['password ' ] = Hash::make ($ fields ['password ' ]);
463515 $ fields ['password_changed_at ' ] = Carbon::now ()->toDateTimeString ();
@@ -466,6 +518,7 @@ public function update(User $user, Request $request)
466518 session ()->forget ('login-error ' );
467519 }
468520 $ original = $ user ->getOriginal ();
521+ $ isLdapUser = $ user ->meta ?->authenticationType === 'ldap ' ;
469522 $ user ->fill ($ fields );
470523 if (array_key_exists ('cell ' , $ fields )) {
471524 $ response = $ this ->validateCellPhoneNumber ($ user , $ fields ['cell ' ]);
@@ -474,28 +527,24 @@ public function update(User $user, Request $request)
474527 }
475528 }
476529 if ($ fields ['email ' ] !== $ original ['email ' ]) {
530+ $ ssoUser = $ isLdapUser ;
477531 if (class_exists (SsoUser::class)) {
478532 // Check if the user is an SSO user (including SAML)
479- $ ssoUser = SsoUser::where ('user_id ' , $ user ->id )->exists ();
480-
481- // Check if the user is an LDAP user
482- if (isset ($ user ->meta ?->authenticationType) && $ user ->meta ->authenticationType === 'ldap ' ) {
483- $ ssoUser = true ;
484- }
485- if ($ ssoUser ) {
486- return response ([
487- 'message ' => __ (
488- "The email can't be edited. This action is only available for SSO-synced users. "
489- ),
490- 'errors ' => [
491- 'email ' => [
492- __ (
493- "The email can't be edited. This action is only available for SSO-synced users. "
494- ),
495- ],
533+ $ ssoUser = $ ssoUser || SsoUser::where ('user_id ' , $ user ->id )->exists ();
534+ }
535+ if ($ ssoUser ) {
536+ return response ([
537+ 'message ' => __ (
538+ "The email can't be edited. This action is only available for SSO-synced users. "
539+ ),
540+ 'errors ' => [
541+ 'email ' => [
542+ __ (
543+ "The email can't be edited. This action is only available for SSO-synced users. "
544+ ),
496545 ],
497- ], 422 );
498- }
546+ ],
547+ ], 422 );
499548 }
500549 if (!isset ($ fields ['valpassword ' ])) {
501550 return response ([
@@ -564,6 +613,59 @@ public function update(User $user, Request $request)
564613 return response ([], 204 );
565614 }
566615
616+ /**
617+ * Authorize and constrain self-service profile updates.
618+ */
619+ private function authorizeSelfServiceUpdate (User $ authenticatedUser , User $ targetUser , array $ fields ): bool
620+ {
621+ $ isSelfServiceUpdate = $ authenticatedUser ->id === $ targetUser ->id
622+ && !$ authenticatedUser ->is_administrator
623+ && !$ authenticatedUser ->hasPermission ('edit-users ' );
624+
625+ if (!$ isSelfServiceUpdate ) {
626+ return false ;
627+ }
628+
629+ if (!$ authenticatedUser ->hasPermission ('edit-personal-profile ' )) {
630+ throw new AuthorizationException (__ ('Not authorized to update this user. ' ));
631+ }
632+
633+ $ disallowedFields = array_diff (array_keys ($ fields ), self ::SELF_SERVICE_UPDATE_FIELDS );
634+ if ($ disallowedFields !== []) {
635+ throw new AuthorizationException (__ ('Not authorized to update one or more user fields. ' ));
636+ }
637+
638+ if (isset ($ fields ['meta ' ]) && is_array ($ fields ['meta ' ])) {
639+ $ disallowedMetaFields = array_diff (array_keys ($ fields ['meta ' ]), self ::SELF_SERVICE_META_FIELDS );
640+ if ($ disallowedMetaFields !== []) {
641+ throw new AuthorizationException (__ ('Not authorized to update one or more user fields. ' ));
642+ }
643+ }
644+
645+ return true ;
646+ }
647+
648+ /**
649+ * Merge self-service metadata into the persisted server-managed values.
650+ */
651+ private function normalizeSelfServiceMeta (User $ user , array $ fields ): array
652+ {
653+ $ meta = (array ) $ user ->meta ;
654+ if (
655+ array_key_exists ('meta ' , $ fields )
656+ && array_key_exists ('disableRecommendations ' , $ fields ['meta ' ])
657+ ) {
658+ if ($ fields ['meta ' ]['disableRecommendations ' ]) {
659+ $ meta ['disableRecommendations ' ] = true ;
660+ } else {
661+ unset($ meta ['disableRecommendations ' ]);
662+ }
663+ }
664+ $ fields ['meta ' ] = $ meta ?: null ;
665+
666+ return $ fields ;
667+ }
668+
567669 /**
568670 * Validate the phone number for SMS two-factor authentication.
569671 *
0 commit comments