parent_slug, __( 'Connections', 'albert-ai-butler' ), __( 'Connections', 'albert-ai-butler' ), 'manage_options', $this->page_slug, [ $this, 'render_page' ] ); } /** * Handle user actions (GET-based). * * @return void * @since 1.0.0 */ public function handle_actions(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified below. if ( ! isset( $_GET['action'] ) || ! isset( $_GET['page'] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified below. if ( $this->page_slug !== $_GET['page'] ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified below. $action = sanitize_key( $_GET['action'] ); switch ( $action ) { case 'revoke': $this->handle_revoke_session(); break; case 'revoke_full': $this->handle_revoke_full_session(); break; case 'revoke_all': $this->handle_revoke_all_sessions(); break; case 'remove_allowed_user': $this->handle_remove_allowed_user(); break; case 'revoke_user_session': $this->handle_revoke_user_session(); break; case 'revoke_all_user_sessions': $this->handle_revoke_all_user_sessions(); break; } } /** * Handle revoking a single session. * * @return void * @since 1.0.0 */ private function handle_revoke_session(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified below. $token_id = isset( $_GET['token_id'] ) ? absint( $_GET['token_id'] ) : 0; if ( ! $token_id ) { return; } // Verify nonce. if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'revoke_my_session_' . $token_id ) ) { wp_die( esc_html__( 'Security check failed.', 'albert-ai-butler' ) ); } // Check permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to revoke sessions.', 'albert-ai-butler' ) ); } global $wpdb; $table = $wpdb->prefix . 'albert_oauth_access_tokens'; // Admins can revoke any connection. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->update( $table, [ 'revoked' => 1 ], [ 'id' => $token_id ], [ '%d' ], [ '%d' ] ); add_settings_error( 'albert_connections', 'session_revoked', __( 'Session revoked successfully.', 'albert-ai-butler' ), 'success' ); // Redirect back. wp_safe_redirect( add_query_arg( [ 'page' => $this->page_slug, 'settings-updated' => 'true', ], admin_url( 'admin.php' ) ) ); exit; } /** * Handle revoking a full session (access + refresh tokens). * * @return void * @since 1.0.0 */ private function handle_revoke_full_session(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified below. $token_id = isset( $_GET['token_id'] ) ? absint( $_GET['token_id'] ) : 0; if ( ! $token_id ) { return; } // Verify nonce. if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'revoke_full_session_' . $token_id ) ) { wp_die( esc_html__( 'Security check failed.', 'albert-ai-butler' ) ); } // Check permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to end sessions.', 'albert-ai-butler' ) ); } global $wpdb; $table = $wpdb->prefix . 'albert_oauth_access_tokens'; // Get the token_id string before revoking. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $token_id_string = $wpdb->get_var( $wpdb->prepare( 'SELECT token_id FROM %i WHERE id = %d', $table, $token_id ) ); // Revoke the access token. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->update( $table, [ 'revoked' => 1 ], [ 'id' => $token_id ], [ '%d' ], [ '%d' ] ); // Revoke associated refresh tokens. if ( $token_id_string ) { $refresh_repo = new RefreshTokenRepository(); $refresh_repo->revokeRefreshTokensByAccessToken( $token_id_string ); } add_settings_error( 'albert_connections', 'session_ended', __( 'Session ended successfully.', 'albert-ai-butler' ), 'success' ); // Redirect back. wp_safe_redirect( add_query_arg( [ 'page' => $this->page_slug, 'settings-updated' => 'true', ], admin_url( 'admin.php' ) ) ); exit; } /** * Handle revoking all sessions. * * @return void * @since 1.0.0 */ private function handle_revoke_all_sessions(): void { // Verify nonce. if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'revoke_all_my_sessions' ) ) { wp_die( esc_html__( 'Security check failed.', 'albert-ai-butler' ) ); } // Check permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to revoke sessions.', 'albert-ai-butler' ) ); } Settings::revoke_user_tokens( get_current_user_id() ); add_settings_error( 'albert_connections', 'all_sessions_revoked', __( 'All sessions revoked successfully.', 'albert-ai-butler' ), 'success' ); // Redirect back. wp_safe_redirect( add_query_arg( [ 'page' => $this->page_slug, 'settings-updated' => 'true', ], admin_url( 'admin.php' ) ) ); exit; } /** * Handle adding a user to the allowed users list. * * @return void * @since 1.0.0 */ public function handle_add_allowed_user(): void { // Verify nonce. if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['albert_add_user_nonce'] ?? '' ) ), 'albert_add_allowed_user' ) ) { wp_die( esc_html__( 'Security check failed.', 'albert-ai-butler' ) ); } // Check permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to manage MCP access.', 'albert-ai-butler' ) ); } $user_id = isset( $_POST['albert_user_id'] ) ? absint( $_POST['albert_user_id'] ) : 0; if ( ! $user_id ) { wp_safe_redirect( add_query_arg( [ 'page' => $this->page_slug ], admin_url( 'admin.php' ) ) ); exit; } // Verify user exists. $user = get_user_by( 'id', $user_id ); if ( ! $user ) { wp_die( esc_html__( 'Invalid user selected.', 'albert-ai-butler' ) ); } // Get current allowed users and add the new one. $allowed_users = get_option( 'albert_allowed_users', [] ); if ( ! in_array( $user_id, $allowed_users, true ) ) { $allowed_users[] = $user_id; update_option( 'albert_allowed_users', $allowed_users ); } // Redirect back. wp_safe_redirect( add_query_arg( [ 'page' => $this->page_slug ], admin_url( 'admin.php' ) ) ); exit; } /** * Handle removing a user from the allowed users list. * * @return void * @since 1.0.0 */ private function handle_remove_allowed_user(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified below. $user_id = isset( $_GET['user_id'] ) ? absint( $_GET['user_id'] ) : 0; if ( ! $user_id ) { return; } // Verify nonce. if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'remove_user_' . $user_id ) ) { wp_die( esc_html__( 'Security check failed.', 'albert-ai-butler' ) ); } // Check permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to manage MCP access.', 'albert-ai-butler' ) ); } // Remove user from allowed list. $allowed_users = get_option( 'albert_allowed_users', [] ); $allowed_users = array_filter( $allowed_users, fn( $id ) => $id !== $user_id ); update_option( 'albert_allowed_users', array_values( $allowed_users ) ); // Revoke all their sessions. Settings::revoke_user_tokens( $user_id ); add_settings_error( 'albert_connections', 'user_removed', __( 'User removed and all their sessions revoked.', 'albert-ai-butler' ), 'success' ); // Redirect back. wp_safe_redirect( add_query_arg( [ 'page' => $this->page_slug, 'settings-updated' => 'true', ], admin_url( 'admin.php' ) ) ); exit; } /** * Handle revoking a single user session. * * @return void * @since 1.0.0 */ private function handle_revoke_user_session(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified below. $token_id = isset( $_GET['token_id'] ) ? absint( $_GET['token_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified below. $user_id = isset( $_GET['user_id'] ) ? absint( $_GET['user_id'] ) : 0; if ( ! $token_id || ! $user_id ) { return; } // Verify nonce. if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'revoke_session_' . $token_id ) ) { wp_die( esc_html__( 'Security check failed.', 'albert-ai-butler' ) ); } // Check permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to revoke sessions.', 'albert-ai-butler' ) ); } global $wpdb; $table = $wpdb->prefix . 'albert_oauth_access_tokens'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->update( $table, [ 'revoked' => 1 ], [ 'id' => $token_id ], [ '%d' ], [ '%d' ] ); add_settings_error( 'albert_connections', 'session_revoked', __( 'Session revoked successfully.', 'albert-ai-butler' ), 'success' ); // Redirect back to user sessions view. wp_safe_redirect( add_query_arg( [ 'page' => $this->page_slug, 'action' => 'view_user_sessions', 'user_id' => $user_id, 'settings-updated' => 'true', ], admin_url( 'admin.php' ) ) ); exit; } /** * Handle revoking all sessions for a user. * * @return void * @since 1.0.0 */ private function handle_revoke_all_user_sessions(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verified below. $user_id = isset( $_GET['user_id'] ) ? absint( $_GET['user_id'] ) : 0; if ( ! $user_id ) { return; } // Verify nonce. if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ?? '' ) ), 'revoke_all_sessions_' . $user_id ) ) { wp_die( esc_html__( 'Security check failed.', 'albert-ai-butler' ) ); } // Check permissions. if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to revoke sessions.', 'albert-ai-butler' ) ); } Settings::revoke_user_tokens( $user_id ); add_settings_error( 'albert_connections', 'all_sessions_revoked', __( 'All sessions revoked successfully.', 'albert-ai-butler' ), 'success' ); // Redirect back to user sessions view. wp_safe_redirect( add_query_arg( [ 'page' => $this->page_slug, 'action' => 'view_user_sessions', 'user_id' => $user_id, 'settings-updated' => 'true', ], admin_url( 'admin.php' ) ) ); exit; } /** * Render the page. * * @return void * @since 1.0.0 */ public function render_page(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'albert-ai-butler' ) ); } // Check if viewing sessions for a specific user. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Just checking for view action. $view_sessions = isset( $_GET['action'] ) && $_GET['action'] === 'view_user_sessions'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Just getting user ID for display. $view_user_id = isset( $_GET['user_id'] ) ? absint( $_GET['user_id'] ) : 0; if ( $view_sessions && $view_user_id ) { $this->render_user_sessions_view( $view_user_id ); return; } $mcp_endpoint = McpServer::get_endpoint_url(); $external_url_fs = McpServer::get_external_url_state(); ?>
render_external_url_filter_notice( $external_url_fs ); ?>
, 2: closing wrapping the filter name. */
esc_html__( 'External override active — MCP endpoint uses the URL returned by the %1$salbert/mcp/external_url%2$s filter.', 'albert-ai-butler' ),
'',
''
);
?>
, 2: closing for the filter name, 3: opening , 4: closing for the invalid value. */
esc_html__( 'The %1$salbert/mcp/external_url%2$s filter returned %3$s%5$s%4$s, which is not a valid URL. Albert is ignoring it and using the default endpoint instead.', 'albert-ai-butler' ),
'',
'',
'',
'',
esc_html( $bad_value )
);
?>
https://, 2: closing , 3: opening http://, 4: closing . */
esc_html__( 'To fix this, make sure the filter returns a full URL with %1$shttps://%2$s (or %3$shttp://%4$s) and no extra characters, or return an empty string to disable the override.', 'albert-ai-butler' ),
'',
'',
'',
''
);
?>
' . esc_html__( 'User not found.', 'albert-ai-butler' ) . '
| display_name ); ?> | |
| user_email ); ?> |