@@ -71,11 +71,27 @@ public function init(): void
7171
7272 public function startup (array $ args ): array
7373 {
74+ $ action = rcube_utils::get_input_value ('_action ' , rcube_utils::INPUT_GPC );
75+
76+ // If admin plugin endpoints are accessed directly without a Roundcube
77+ // session, start OIDC login flow instead of failing with access errors.
78+ if ($ this ->rc ->task === 'settings '
79+ && is_string ($ action )
80+ && in_array ($ action , [
81+ self ::ACTION_ADMIN ,
82+ self ::ACTION_ADMIN_SAVE_POLICY ,
83+ self ::ACTION_ADMIN_DELETE_USER ,
84+ self ::ACTION_ADMIN_SET_USER_STATUS ,
85+ ], true )
86+ && empty ($ _SESSION ['user_id ' ])
87+ ) {
88+ $ this ->redirectTo ($ this ->urlForAction (self ::ACTION_LOGIN ));
89+ }
90+
7491 if ($ this ->rc ->task !== 'login ' || !empty ($ _SESSION ['user_id ' ])) {
7592 return $ args ;
7693 }
7794
78- $ action = rcube_utils::get_input_value ('_action ' , rcube_utils::INPUT_GPC );
7995 if (is_string ($ action ) && strpos ($ action , 'plugin.universal_oidc_mail_sso_ ' ) === 0 ) {
8096 // Fallback direct dispatcher for environments where register_action
8197 // doesn't route plugin actions in login task as expected.
@@ -461,7 +477,7 @@ public function actionSaveMailbox(): void
461477 return ;
462478 }
463479
464- if ($ email !== strtolower ((string ) $ oidcEmail )) {
480+ if ($ this -> isStrictOidcEmailBinding () && $ email !== strtolower ((string ) $ oidcEmail )) {
465481 $ this ->renderConnectMailboxPage ('Email must match your OIDC account. ' );
466482 return ;
467483 }
@@ -577,12 +593,17 @@ private function renderConnectMailboxPage(string $message, bool $isError = true,
577593 $ imapSecurity = (string ) ($ values ['imap_security ' ] ?? $ this ->cfg ('default_imap_security ' , 'ssl ' ));
578594 $ smtpSecurity = (string ) ($ values ['smtp_security ' ] ?? $ this ->cfg ('default_smtp_security ' , 'tls ' ));
579595 $ smtpAuthChecked = (string ) ($ values ['smtp_auth ' ] ?? (((string ) $ this ->cfg ('default_smtp_auth ' , '1 ' ) !== '0 ' ) ? '1 ' : '0 ' ));
596+ $ strictEmailBinding = $ this ->isStrictOidcEmailBinding ();
580597
581598 $ replacements = [
582599 '{{form_action}} ' => htmlspecialchars ($ this ->rc ->url (['task ' => 'login ' , 'action ' => self ::ACTION_SAVE_MAILBOX ]), ENT_QUOTES , 'UTF-8 ' ),
583600 '{{csrf_token}} ' => htmlspecialchars (rcmail::get_instance ()->get_request_token (), ENT_QUOTES , 'UTF-8 ' ),
584601 '{{message}} ' => $ message !== '' ? '<div class=" ' . ($ isError ? 'pizsso-error ' : 'pizsso-success ' ) . '"> ' . htmlspecialchars ($ message , ENT_QUOTES , 'UTF-8 ' ) . '</div> ' : '' ,
585602 '{{email}} ' => htmlspecialchars ($ email , ENT_QUOTES , 'UTF-8 ' ),
603+ '{{email_readonly_attr}} ' => $ strictEmailBinding ? 'readonly ' : '' ,
604+ '{{email_policy_hint}} ' => $ strictEmailBinding
605+ ? '<div class="hint">Email is locked to your verified OIDC account by admin policy.</div> '
606+ : '<div class="hint">Admin policy allows overriding mailbox email.</div> ' ,
586607 '{{imap_host}} ' => htmlspecialchars ((string ) ($ values ['imap_host ' ] ?? $ this ->cfg ('default_imap_host ' , 'imap.example.com ' )), ENT_QUOTES , 'UTF-8 ' ),
587608 '{{imap_port}} ' => htmlspecialchars ((string ) ($ values ['imap_port ' ] ?? $ this ->cfg ('default_imap_port ' , '993 ' )), ENT_QUOTES , 'UTF-8 ' ),
588609 '{{imap_security_ssl_selected}} ' => ($ imapSecurity === 'ssl ' ) ? 'selected ' : '' ,
@@ -700,6 +721,7 @@ private function getPolicies(): array
700721 'allowed_email_domains ' ,
701722 'allowed_imap_hosts ' ,
702723 'allowed_smtp_hosts ' ,
724+ 'allow_custom_mailbox_email ' ,
703725 ]);
704726
705727 return $ this ->policyCache ;
@@ -913,6 +935,18 @@ private function isAdminUser(): bool
913935 return false ;
914936 }
915937
938+ private function isStrictOidcEmailBinding (): bool
939+ {
940+ $ policies = $ this ->getPolicies ();
941+ $ policyVal = strtolower (trim ((string ) ($ policies ['allow_custom_mailbox_email ' ] ?? '' )));
942+ if ($ policyVal !== '' ) {
943+ return !in_array ($ policyVal , ['1 ' , 'true ' , 'yes ' , 'on ' ], true );
944+ }
945+
946+ // Strict by default unless explicitly disabled.
947+ return !$ this ->cfgBool ('allow_custom_mailbox_email ' , false );
948+ }
949+
916950 public function actionAdminDashboard (): void
917951 {
918952 if ($ this ->rc ->task !== 'settings ' ) {
@@ -993,6 +1027,10 @@ public function actionAdminDashboard(): void
9931027 '{{allowed_domains}} ' => htmlspecialchars ((string ) ($ policies ['allowed_email_domains ' ] ?? '' ), ENT_QUOTES , 'UTF-8 ' ),
9941028 '{{allowed_imap_hosts}} ' => htmlspecialchars ((string ) ($ policies ['allowed_imap_hosts ' ] ?? '' ), ENT_QUOTES , 'UTF-8 ' ),
9951029 '{{allowed_smtp_hosts}} ' => htmlspecialchars ((string ) ($ policies ['allowed_smtp_hosts ' ] ?? '' ), ENT_QUOTES , 'UTF-8 ' ),
1030+ '{{allow_custom_mailbox_email_checked}} ' => !empty ($ policies ['allow_custom_mailbox_email ' ])
1031+ && in_array (strtolower ((string ) $ policies ['allow_custom_mailbox_email ' ]), ['1 ' , 'true ' , 'yes ' , 'on ' ], true )
1032+ ? 'checked '
1033+ : '' ,
9961034 ]);
9971035
9981036 header ('Content-Type: text/html; charset=UTF-8 ' );
@@ -1015,10 +1053,12 @@ public function actionAdminSavePolicy(): void
10151053 $ allowedDomains = trim ((string ) rcube_utils::get_input_value ('allowed_email_domains ' , rcube_utils::INPUT_POST ));
10161054 $ allowedImapHosts = trim ((string ) rcube_utils::get_input_value ('allowed_imap_hosts ' , rcube_utils::INPUT_POST ));
10171055 $ allowedSmtpHosts = trim ((string ) rcube_utils::get_input_value ('allowed_smtp_hosts ' , rcube_utils::INPUT_POST ));
1056+ $ allowCustomMailboxEmail = rcube_utils::get_input_value ('allow_custom_mailbox_email ' , rcube_utils::INPUT_POST ) ? '1 ' : '0 ' ;
10181057
10191058 $ this ->storage ->setPolicy ('allowed_email_domains ' , $ allowedDomains );
10201059 $ this ->storage ->setPolicy ('allowed_imap_hosts ' , $ allowedImapHosts );
10211060 $ this ->storage ->setPolicy ('allowed_smtp_hosts ' , $ allowedSmtpHosts );
1061+ $ this ->storage ->setPolicy ('allow_custom_mailbox_email ' , $ allowCustomMailboxEmail );
10221062 $ this ->policyCache = null ;
10231063
10241064 $ this ->audit ('admin_policy ' , 'ok ' , 'policy updated ' );
@@ -1163,6 +1203,7 @@ private function cfg(string $name, ?string $default = null): ?string
11631203 'redirect_uri ' => 'OIDC_REDIRECT_URI ' ,
11641204 'post_logout_redirect_uri ' => 'OIDC_POST_LOGOUT_REDIRECT_URI ' ,
11651205 'allowed_email_domain ' => 'ALLOWED_EMAIL_DOMAIN ' ,
1206+ 'allow_custom_mailbox_email ' => 'ALLOW_CUSTOM_MAILBOX_EMAIL ' ,
11661207 'mailbox_key ' => 'RCUBE_MAILBOX_KEY ' ,
11671208 'force_https ' => 'FORCE_HTTPS ' ,
11681209 'disable_password_login ' => 'DISABLE_PASSWORD_LOGIN ' ,
0 commit comments