Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions inc/Abilities/AgentAbilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,14 +401,21 @@ public static function renameAgent( array $input ): array {
}

/**
* List all registered agents.
* List registered agents, scoped to the current site.
*
* On multisite, returns agents with site_scope matching the current blog_id
* OR site_scope IS NULL (network-wide). This mirrors WordPress core's default
* of scoping user queries to the current site via wp_N_capabilities meta.
*
* @since 0.38.0
* @since 0.57.0 Added site_scope filtering and site_scope in output.
*
* @param array $input Input parameters (unused).
* @return array Result.
*/
public static function listAgents( array $input ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Required by WP_Ability interface.
$agents_repo = new Agents();
$rows = $agents_repo->get_all();
$rows = $agents_repo->get_all( array( 'site_id' => get_current_blog_id() ) );

$agents = array();

Expand All @@ -418,6 +425,7 @@ public static function listAgents( array $input ): array { // phpcs:ignore Gener
'agent_slug' => (string) $row['agent_slug'],
'agent_name' => (string) $row['agent_name'],
'owner_id' => (int) $row['owner_id'],
'site_scope' => isset( $row['site_scope'] ) ? (int) $row['site_scope'] : null,
'status' => (string) $row['status'],
);
}
Expand Down
13 changes: 11 additions & 2 deletions inc/Abilities/PermissionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,10 @@ public static function owns_resource( int $resource_user_id, string $action = 'm
* Determines which agent's data should be returned based on the request:
* - If `agent_id` param is present and caller has access → use that agent_id
* - If caller is admin and no `agent_id` param → return null (all agents)
* - If caller is non-admin → resolve their accessible agent IDs
* - If caller is non-admin → resolve via ownership, then access grants
*
* @since 0.41.0
* @since 0.57.0 Non-admin fallback checks access grants when user owns no agent.
*
* @param \WP_REST_Request $request REST request.
* @param string $action Action key for admin check (default: 'manage_flows').
Expand Down Expand Up @@ -402,7 +403,7 @@ public static function resolve_scoped_agent_id( \WP_REST_Request $request, strin
return null;
}

// Non-admin with no explicit agent_id: resolve via owner_id lookup.
// Non-admin with no explicit agent_id: resolve via owner_id first.
$user_id = self::acting_user_id();
$agents_repo = new \DataMachine\Core\Database\Agents\Agents();
$agent = $agents_repo->get_by_owner_id( $user_id );
Expand All @@ -411,6 +412,14 @@ public static function resolve_scoped_agent_id( \WP_REST_Request $request, strin
return (int) $agent['agent_id'];
}

// Fallback: check access grants (user may have access to an agent they don't own).
$access_repo = new \DataMachine\Core\Database\Agents\AgentAccess();
$accessible_ids = $access_repo->get_agent_ids_for_user( $user_id );

if ( ! empty( $accessible_ids ) ) {
return $accessible_ids[0];
}

// No agent found — return 0 which will match nothing (safe fallback).
return 0;
}
Expand Down
28 changes: 23 additions & 5 deletions inc/Api/Agents.php
Original file line number Diff line number Diff line change
Expand Up @@ -298,17 +298,25 @@ public static function register_routes(): void {
/**
* Handle GET /agents — list agents the current user can access.
*
* Admins see all agents. Other users see only agents they have access to.
* Mirrors WordPress core's multisite user scoping: queries are scoped to the
* current site by default. Agents with site_scope matching the current blog_id
* OR site_scope IS NULL (network-wide) are returned. Admins see all matching
* agents; non-admins see only agents they have explicit access grants for.
*
* @since 0.41.0
* @since 0.57.0 Added site_scope filtering based on current blog_id.
*
* @param WP_REST_Request $request REST request.
* @return \WP_REST_Response|WP_Error
*/
public static function handle_list( WP_REST_Request $request ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
$agents_repo = new AgentsRepository();
$user_id = get_current_user_id();
$agents_repo = new AgentsRepository();
$user_id = get_current_user_id();
$current_site = get_current_blog_id();

if ( PermissionHelper::can( 'manage_agents' ) ) {
$all_agents = $agents_repo->get_all();
// Admins see agents scoped to the current site + network-wide agents.
$all_agents = $agents_repo->get_all( array( 'site_id' => $current_site ) );
} else {
$access_repo = new AgentAccess();
$accessible_ids = $access_repo->get_agent_ids_for_user( $user_id );
Expand All @@ -322,10 +330,16 @@ public static function handle_list( WP_REST_Request $request ) { // phpcs:ignore
);
}

// Filter accessible agents by site scope (current site + network-wide).
$all_agents = array();
foreach ( $accessible_ids as $agent_id ) {
$agent = $agents_repo->get_agent( $agent_id );
if ( $agent ) {
if ( ! $agent ) {
continue;
}

$scope = $agent['site_scope'] ?? null;
if ( null === $scope || (int) $scope === $current_site ) {
$all_agents[] = $agent;
}
}
Expand Down Expand Up @@ -716,6 +730,9 @@ public static function handle_revoke_token( WP_REST_Request $request ) {
/**
* Shape an agent row for list output (excludes config which may contain secrets).
*
* @since 0.41.0
* @since 0.57.0 Added site_scope to output.
*
* @param array $agent Agent database row.
* @return array Shaped output.
*/
Expand All @@ -725,6 +742,7 @@ private static function shape_list_item( array $agent ): array {
'agent_slug' => (string) $agent['agent_slug'],
'agent_name' => (string) $agent['agent_name'],
'owner_id' => (int) $agent['owner_id'],
'site_scope' => isset( $agent['site_scope'] ) ? (int) $agent['site_scope'] : null,
'status' => (string) $agent['status'],
'created_at' => $agent['created_at'] ?? '',
'updated_at' => $agent['updated_at'] ?? '',
Expand Down
46 changes: 38 additions & 8 deletions inc/Core/Database/Agents/Agents.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,18 +248,48 @@ public function update_agent( int $agent_id, array $data ): bool {
}

/**
* Get all agents.
* Get all agents, optionally filtered by site scope.
*
* Mirrors WordPress core's multisite user scoping pattern:
* - Default (no args): returns ALL agents (network-wide view)
* - With site_id: returns agents scoped to that site OR network-wide (site_scope IS NULL)
*
* @since 0.38.0
* @since 0.57.0 Added $args parameter with site_id filtering.
*
* @param array $args {
* Optional. Query arguments.
*
* @type int|null $site_id Blog ID to filter by. Agents with this site_scope
* OR site_scope IS NULL (network-wide) are returned.
* Default null (no filtering — all agents).
* }
* @return array List of agent rows.
*/
public function get_all(): array {
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
$rows = $this->wpdb->get_results(
$this->wpdb->prepare( 'SELECT * FROM %i ORDER BY agent_id ASC', $this->table_name ),
ARRAY_A
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
public function get_all( array $args = array() ): array {
$site_id = $args['site_id'] ?? null;

if ( null !== $site_id ) {
$site_id = (int) $site_id;

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
$rows = $this->wpdb->get_results(
$this->wpdb->prepare(
'SELECT * FROM %i WHERE site_scope = %d OR site_scope IS NULL ORDER BY agent_id ASC',
$this->table_name,
$site_id
),
ARRAY_A
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
} else {
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
$rows = $this->wpdb->get_results(
$this->wpdb->prepare( 'SELECT * FROM %i ORDER BY agent_id ASC', $this->table_name ),
ARRAY_A
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
}

if ( ! $rows ) {
return array();
Expand Down
Loading