'boolean',
'sanitize_callback' => 'rest_sanitize_boolean',
'default' => false,
)
);
}
/**
* Add admin menu
*/
public static function add_admin_menu() {
add_submenu_page(
'abilities-bridge',
__( 'Ability Permissions', 'abilities-bridge' ),
__( 'Ability Permissions', 'abilities-bridge' ),
'manage_options',
'abilities-bridge-permissions',
array( __CLASS__, 'render_permissions_page' )
);
}
/**
* Enqueue admin assets.
*
* @param string $hook The current admin page hook.
* @return void
*/
public static function enqueue_admin_assets( $hook ) {
if ( strpos( $hook, 'abilities-bridge-permissions' ) === false ) {
return;
}
wp_enqueue_style(
'abilities-bridge-permissions',
ABILITIES_BRIDGE_PLUGIN_URL . 'admin/css/ability-permissions.css',
array(),
filemtime( ABILITIES_BRIDGE_PLUGIN_DIR . 'admin/css/ability-permissions.css' )
);
wp_enqueue_script(
'abilities-bridge-permissions',
ABILITIES_BRIDGE_PLUGIN_URL . 'admin/js/ability-permissions.js',
array( 'jquery' ),
filemtime( ABILITIES_BRIDGE_PLUGIN_DIR . 'admin/js/ability-permissions.js' ),
true
);
wp_localize_script(
'abilities-bridge-permissions',
'abilitiesBridgeAbilities',
array(
'abilitiesConsentGiven' => (bool) get_option( 'abilities_bridge_abilities_api_consent', false ),
'abilitiesApiAvailable' => function_exists( 'wp_get_ability' ),
'availableAbilities' => Abilities_Bridge_Ability_Permissions::get_registered_ability_names(),
)
);
}
/**
* Verify the page navigation nonce.
*
* Returns true if nonce is valid or not present (first page load).
* Dies with error if nonce is present but invalid.
*
* @return bool True if nonce is valid, false if no nonce present.
*/
private static function verify_page_nonce() {
$nonce = filter_input( INPUT_GET, '_wpnonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
// No nonce present = first page load, allow with defaults.
if ( empty( $nonce ) ) {
return false;
}
// Nonce present - verify it.
if ( ! wp_verify_nonce( $nonce, 'abilities_bridge_permissions_nav' ) ) {
wp_die(
esc_html__( 'Security verification failed. Please use the navigation links provided.', 'abilities-bridge' ),
esc_html__( 'Security Check Failed', 'abilities-bridge' ),
array( 'response' => 403 )
);
}
return true;
}
/**
* Get a sanitized GET parameter for display/routing purposes.
*
* Verifies nonce before accessing parameters. If no nonce is present
* (first page load), returns the default value.
*
* @param string $key Parameter key.
* @param string $default Default value.
* @return string Sanitized value.
*/
private static function get_url_param( $key, $default = '' ) {
// Verify nonce - returns false if no nonce (first load).
if ( ! self::verify_page_nonce() ) {
return $default;
}
$value = filter_input( INPUT_GET, $key, FILTER_SANITIZE_FULL_SPECIAL_CHARS );
return ! empty( $value ) ? sanitize_key( $value ) : $default;
}
/**
* Get a sanitized integer GET parameter for display purposes.
*
* Verifies nonce before accessing parameters. If no nonce is present
* (first page load), returns the default value.
*
* @param string $key Parameter key.
* @param int $default Default value.
* @return int Sanitized integer value.
*/
private static function get_url_int_param( $key, $default = 0 ) {
// Verify nonce - returns false if no nonce (first load).
if ( ! self::verify_page_nonce() ) {
return $default;
}
$value = filter_input( INPUT_GET, $key, FILTER_VALIDATE_INT );
return false !== $value && null !== $value ? max( 1, absint( $value ) ) : $default;
}
/**
* Render permissions page
*/
public static function render_permissions_page() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'abilities-bridge' ) );
}
$active_tab = self::get_url_param( 'tab', 'list' );
?>
đĄī¸ Security Architecture:
Every ability execution must pass ALL 7 hardcoded permission gates. Default is DENY, explicit ALLOW only.
|
|
|
|
|
|
esc_like( 'ability_executed_' ) . '%';
$usage_counts = $wpdb->get_results(
$wpdb->prepare(
"SELECT
REPLACE(action, 'ability_executed_', '') as ability_name,
COUNT(*) as count
FROM %i
WHERE action LIKE %s
AND DATE(created_at) = %s
GROUP BY action",
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ),
$like_pattern,
$today
),
OBJECT_K
);
foreach ( $permissions as $perm ) :
// Get count from pre-loaded array (no query in loop).
$today_count = isset( $usage_counts[ $perm['ability_name'] ] ) ? $usage_counts[ $perm['ability_name'] ]->count : 0;
$usage_percent = $perm['max_per_day'] > 0 ? ( $today_count / $perm['max_per_day'] ) * 100 : 0;
$usage_class = $usage_percent >= 90 ? 'high' : ( $usage_percent >= 70 ? 'medium' : 'low' );
?>
|
|
â Enabled
â Disabled
|
|
0 ) {
echo esc_html( $perm['max_per_day'] . '/day' );
} else {
echo 'Disabled';
}
?>
|
'abilities-bridge-permissions',
'tab' => 'register',
'edit' => rawurlencode( $perm['ability_name'] ),
'_wpnonce' => wp_create_nonce( 'abilities_bridge_permissions_nav' ),
'_edit_nonce' => wp_create_nonce( 'abilities_bridge_edit_ability' ),
),
admin_url( 'admin.php' )
);
?>
|
403 )
);
}
// Now safe to use user input after nonce verified.
$ability_name = sanitize_text_field( wp_unslash( $_GET['edit'] ) );
$editing = true;
global $wpdb;
$edit_data = $wpdb->get_row(
$wpdb->prepare( 'SELECT * FROM %i WHERE ability_name = %s', Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_ABILITY_PERMISSIONS ), $ability_name ),
ARRAY_A
);
// If ability not found, show error and stop editing mode.
if ( ! $edit_data ) {
$editing = false;
?>
get_var(
$wpdb->prepare(
'SELECT COUNT(*) FROM %i
WHERE action LIKE %s',
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ),
$wpdb->esc_like( 'ability_executed_' ) . '%'
)
);
$today_executions = $wpdb->get_var(
$wpdb->prepare(
'SELECT COUNT(*) FROM %i
WHERE action LIKE %s
AND DATE(created_at) = %s',
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ),
$wpdb->esc_like( 'ability_executed_' ) . '%',
current_time( 'Y-m-d' )
)
);
$violations = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM %i
WHERE action = 'permission_violation'",
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS )
)
);
// Get source breakdown.
$admin_executions = $wpdb->get_var(
$wpdb->prepare(
'SELECT COUNT(*) FROM %i
WHERE action LIKE %s
AND source = %s',
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ),
$wpdb->esc_like( 'ability_executed_' ) . '%',
'admin'
)
);
$mcp_executions = $wpdb->get_var(
$wpdb->prepare(
'SELECT COUNT(*) FROM %i
WHERE action LIKE %s
AND source = %s',
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ),
$wpdb->esc_like( 'ability_executed_' ) . '%',
'mcp'
)
);
// Get top abilities.
$top_abilities = $wpdb->get_results(
$wpdb->prepare(
"SELECT
REPLACE(action, 'ability_executed_', '') as ability_name,
COUNT(*) as execution_count,
MAX(created_at) as last_executed
FROM %i
WHERE action LIKE %s
AND created_at > DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY action
ORDER BY execution_count DESC
LIMIT 10",
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ),
$wpdb->esc_like( 'ability_executed_' ) . '%'
)
);
?>
|
|
|
ability_name ); ?> |
execution_count ) ); ?> |
last_executed ), time() ) ); ?> ago |
get_results(
$wpdb->prepare(
'SELECT * FROM %i
WHERE action LIKE %s OR action = %s
ORDER BY created_at DESC
LIMIT %d OFFSET %d',
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ),
$wpdb->esc_like( 'ability_' ) . '%',
'permission_violation',
$per_page,
$offset
)
);
$total_logs = $wpdb->get_var(
$wpdb->prepare(
'SELECT COUNT(*) FROM %i
WHERE action LIKE %s OR action = %s',
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_LOGS ),
$wpdb->esc_like( 'ability_' ) . '%',
'permission_violation'
)
);
$total_pages = ceil( $total_logs / $per_page );
?>
|
|
|
|
|
| created_at ), time() ) ); ?> ago |
username ); ?> |
action, 'success' ) !== false ) {
$action_class .= ' log-success';
} elseif ( strpos( $log->action, 'failed' ) !== false || 'permission_violation' === $log->action ) {
$action_class .= ' log-error';
}
?>
action, '_' ) ) ); ?>
|
function_name ); ?> |
error_message ) : ?>
error_message ); ?>
function_output ) : ?>
â
|
1 ) : ?>
$base_url,
'format' => '',
'prev_text' => __( '«', 'abilities-bridge' ),
'next_text' => __( '»', 'abilities-bridge' ),
'total' => (int) $total_pages,
'current' => $paged,
)
)
);
?>
$enabled,
'max_per_day' => $max_per_day,
'max_per_hour' => $max_per_hour,
'risk_level' => $risk_level,
'min_capability' => $min_capability,
'description' => $description,
'reason_for_approval' => $reason_for_approval,
)
);
if ( is_wp_error( $result ) ) {
wp_safe_redirect(
add_query_arg(
array(
'page' => 'abilities-bridge-permissions',
'tab' => 'register',
'error' => rawurlencode( $result->get_error_message() ),
),
admin_url( 'admin.php' )
)
);
} else {
wp_safe_redirect(
add_query_arg(
array(
'page' => 'abilities-bridge-permissions',
'tab' => 'list',
'success' => 'registered',
),
admin_url( 'admin.php' )
)
);
}
exit;
}
/**
* Handle ability toggle
*/
public static function handle_toggle_ability() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions.', 'abilities-bridge' ) );
}
check_admin_referer( 'abilities_bridge_toggle_ability' );
$ability_name = isset( $_POST['ability'] ) ? sanitize_text_field( wp_unslash( $_POST['ability'] ) ) : '';
$enable = isset( $_POST['enable'] ) ? intval( wp_unslash( $_POST['enable'] ) ) : 0;
if ( $enable ) {
Abilities_Bridge_Ability_Permissions::enable_ability( $ability_name );
} else {
Abilities_Bridge_Ability_Permissions::disable_ability( $ability_name );
}
wp_safe_redirect(
add_query_arg(
array(
'page' => 'abilities-bridge-permissions',
'success' => $enable ? 'enabled' : 'disabled',
),
admin_url( 'admin.php' )
)
);
exit;
}
/**
* Handle ability deletion
*/
public static function handle_delete_ability() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions.', 'abilities-bridge' ) );
}
check_admin_referer( 'abilities_bridge_delete_ability' );
$ability_name = isset( $_POST['ability'] ) ? sanitize_text_field( wp_unslash( $_POST['ability'] ) ) : '';
global $wpdb;
$wpdb->delete(
Abilities_Bridge_Database::table( Abilities_Bridge_Database::TABLE_ABILITY_PERMISSIONS ),
array( 'ability_name' => $ability_name ),
array( '%s' )
);
wp_safe_redirect(
add_query_arg(
array(
'page' => 'abilities-bridge-permissions',
'success' => 'deleted',
),
admin_url( 'admin.php' )
)
);
exit;
}
/**
* Handle enable/disable abilities API toggle
*/
public static function handle_toggle_abilities_api() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions.', 'abilities-bridge' ) );
}
check_admin_referer( 'abilities_bridge_toggle_abilities_api' );
$enabled = isset( $_POST['abilities_bridge_enable_abilities_api'] ) && sanitize_text_field( wp_unslash( $_POST['abilities_bridge_enable_abilities_api'] ) ) === '1';
$consent_submitted = isset( $_POST['abilities_bridge_abilities_api_consent'] ) && sanitize_text_field( wp_unslash( $_POST['abilities_bridge_abilities_api_consent'] ) ) === '1';
$consent_already_given = get_option( 'abilities_bridge_abilities_api_consent', false );
// Validate: If trying to enable, consent must be checked (or already given).
if ( $enabled && ! $consent_submitted && ! $consent_already_given ) {
wp_safe_redirect(
add_query_arg(
array(
'page' => 'abilities-bridge-permissions',
'tab' => 'list',
'error' => 'abilities_consent_required',
),
admin_url( 'admin.php' )
)
);
exit;
}
// Persist consent if newly given (one-time consent).
if ( $consent_submitted && ! $consent_already_given ) {
update_option( 'abilities_bridge_abilities_api_consent', true );
}
// Update abilities API setting.
update_option( 'abilities_bridge_enable_abilities_api', $enabled );
wp_safe_redirect(
add_query_arg(
array(
'page' => 'abilities-bridge-permissions',
'tab' => 'list',
'success' => $enabled ? 'abilities_enabled' : 'abilities_disabled',
),
admin_url( 'admin.php' )
)
);
exit;
}
/**
* Handle core abilities enable/disable toggle
*/
public static function handle_toggle_core_abilities() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'You do not have sufficient permissions.', 'abilities-bridge' ) );
}
check_admin_referer( 'abilities_bridge_toggle_core_abilities', 'toggle_core_abilities_nonce' );
$core_abilities = array( 'core/get-site-info', 'core/get-user-info', 'core/get-environment-info' );
$all_permissions = Abilities_Bridge_Ability_Permissions::get_all_permissions( false );
$changes_made = array();
foreach ( $core_abilities as $ability_name ) {
$field_name = 'core_ability_' . $ability_name;
if ( isset( $_POST[ $field_name ] ) ) {
$enable = intval( wp_unslash( $_POST[ $field_name ] ) );
// Check if ability is already registered.
$permission = null;
foreach ( $all_permissions as $perm ) {
if ( $perm['ability_name'] === $ability_name ) {
$permission = $perm;
break;
}
}
if ( ! $permission ) {
// Auto-register with safe defaults if not exists.
Abilities_Bridge_Ability_Permissions::register_ability(
$ability_name,
array(
'enabled' => $enable,
'max_per_day' => 100,
'max_per_hour' => 50,
'risk_level' => 'low',
'min_capability' => null,
'description' => self::get_core_ability_description( $ability_name ),
'reason_for_approval' => 'Core read-only ability enabled via UI',
)
);
$changes_made[] = $ability_name . ' registered and ' . ( $enable ? 'enabled' : 'disabled' );
} elseif ( $enable && ! $permission['enabled'] ) {
// Enable existing ability.
Abilities_Bridge_Ability_Permissions::enable_ability( $ability_name );
$changes_made[] = $ability_name . ' enabled';
} elseif ( ! $enable && $permission['enabled'] ) {
// Disable existing ability.
Abilities_Bridge_Ability_Permissions::disable_ability( $ability_name );
$changes_made[] = $ability_name . ' disabled';
}
}
}
$message = ! empty( $changes_made ) ? 'core_abilities_updated' : 'no_changes';
wp_safe_redirect(
add_query_arg(
array(
'page' => 'abilities-bridge-permissions',
'tab' => 'list',
'success' => $message,
),
admin_url( 'admin.php' )
)
);
exit;
}
/**
* Get description for core abilities.
*
* @param string $ability_name The ability name.
* @return string The ability description.
*/
private static function get_core_ability_description( $ability_name ) {
$descriptions = array(
'core/get-site-info' => 'Get WordPress site information',
'core/get-user-info' => 'Get current user information',
'core/get-environment-info' => 'Get server environment information',
);
return isset( $descriptions[ $ability_name ] ) ? $descriptions[ $ability_name ] : 'Core read-only ability';
}
}
// Initialize admin interface.
Abilities_Bridge_Ability_Permissions_Admin::init();