namespace = 'agenticdaisy-firewall/v1'; $this->rest_base = 'agents'; } /** * Register routes. */ public function register_routes(): void { register_rest_route( 'agenticdaisy-firewall/v1', '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'permissions_check' ), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( $this, 'permissions_check' ), 'args' => array( 'name' => array( 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => 'rest_validate_request_arg', ), 'agent_key' => array( 'type' => 'string', 'required' => true, 'pattern' => '^[a-z0-9-]+$', 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ), 'wp_user_id' => array( 'type' => 'integer', 'required' => true, 'minimum' => 1, 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ), 'scopes' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), 'default' => array(), 'validate_callback' => 'rest_validate_request_arg', ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( 'agenticdaisy-firewall/v1', '/' . $this->rest_base . '/(?P\d+)', array( array( 'methods' => 'PUT', 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( $this, 'permissions_check' ), 'args' => array( 'status' => array( 'type' => 'string', 'required' => true, 'enum' => array( 'active', 'disabled' ), 'sanitize_callback' => 'sanitize_key', 'validate_callback' => 'rest_validate_request_arg', ), ), ), ) ); register_rest_route( 'agenticdaisy-firewall/v1', '/' . $this->rest_base . '/(?P\d+)/token', array( array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'issue_token' ), 'permission_callback' => array( $this, 'permissions_check' ), ), array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'revoke_token' ), 'permission_callback' => array( $this, 'permissions_check' ), ), ) ); } /** * Admin capability gate; never __return_true (checklist ยง1.14). * * @param WP_REST_Request $request Request. * @return true|\WP_Error */ public function permissions_check( $request ) { if ( current_user_can( 'manage_options' ) ) { return true; } return new \WP_Error( 'rest_forbidden', __( 'You are not allowed to manage agents.', 'agentic-daisy-ai-agent-firewall' ), array( 'status' => rest_authorization_required_code() ) ); } /** * List agents. * * @param WP_REST_Request $request Request. */ public function get_items( $request ): WP_REST_Response { return new WP_REST_Response( array_map( array( $this, 'prepare_agent' ), $this->agents->all() ), 200 ); } /** * Create an agent. * * @param WP_REST_Request $request Request. * @return WP_REST_Response|\WP_Error */ public function create_item( $request ) { $agent_key = (string) $request->get_param( 'agent_key' ); $wp_user_id = (int) $request->get_param( 'wp_user_id' ); if ( null !== $this->agents->find_by_key( $agent_key ) ) { return new \WP_Error( 'adaf_agent_key_taken', __( 'An agent with this key already exists.', 'agentic-daisy-ai-agent-firewall' ), array( 'status' => 409 ) ); } if ( ! get_user_by( 'id', $wp_user_id ) ) { return new \WP_Error( 'adaf_invalid_user', __( 'The linked WordPress user does not exist.', 'agentic-daisy-ai-agent-firewall' ), array( 'status' => 400 ) ); } $scopes = $request->get_param( 'scopes' ); $scopes = is_array( $scopes ) ? array_values( array_map( 'sanitize_text_field', array_map( 'strval', $scopes ) ) ) : array(); $agent = $this->agents->create( $agent_key, (string) $request->get_param( 'name' ), $wp_user_id, null, new DateTimeImmutable( 'now' ), $scopes ); return new WP_REST_Response( $this->prepare_agent( $agent ), 201 ); } /** * Update an agent's status. * * @param WP_REST_Request $request Request. * @return WP_REST_Response|\WP_Error */ public function update_item( $request ) { $id = (int) $request->get_param( 'id' ); if ( null === $this->agents->find_by_id( $id ) ) { return $this->not_found(); } $this->agents->update_status( $id, (string) $request->get_param( 'status' ) ); $updated = $this->agents->find_by_id( $id ); assert( $updated instanceof Agent ); return new WP_REST_Response( $this->prepare_agent( $updated ), 200 ); } /** * Issue (or rotate) the agent's token; plaintext returned exactly once. * * @param WP_REST_Request $request Request. * @return WP_REST_Response|\WP_Error */ public function issue_token( $request ) { $id = (int) $request->get_param( 'id' ); if ( null === $this->agents->find_by_id( $id ) ) { return $this->not_found(); } $token = $this->tokens->issue( $id ); return new WP_REST_Response( array( 'token' => $token, 'message' => __( 'Store this token now; it cannot be shown again.', 'agentic-daisy-ai-agent-firewall' ), ), 201 ); } /** * Revoke the agent's token. * * @param WP_REST_Request $request Request. * @return WP_REST_Response|\WP_Error */ public function revoke_token( $request ) { $id = (int) $request->get_param( 'id' ); if ( null === $this->agents->find_by_id( $id ) ) { return $this->not_found(); } $this->tokens->revoke( $id ); return new WP_REST_Response( array( 'revoked' => true ), 200 ); } /** * 404 helper. */ private function not_found(): \WP_Error { return new \WP_Error( 'adaf_not_found', __( 'Agent not found.', 'agentic-daisy-ai-agent-firewall' ), array( 'status' => 404 ) ); } /** * Serialize an agent. Never exposes the token or its hash. * * @param Agent $agent Agent. * @return array */ private function prepare_agent( Agent $agent ): array { return array( 'id' => $agent->id, 'agent_key' => $agent->agent_key, 'name' => $agent->name, 'wp_user_id' => $agent->wp_user_id, 'status' => $agent->status, 'has_token' => null !== $agent->token_hash, 'scopes' => $agent->scopes, 'created_at' => $agent->created_at->format( 'Y-m-d H:i:s' ), ); } /** * Item schema. * * @return array */ public function get_item_schema(): array { return array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'adaf_agent', 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'integer' ), 'agent_key' => array( 'type' => 'string' ), 'name' => array( 'type' => 'string' ), 'wp_user_id' => array( 'type' => 'integer' ), 'status' => array( 'type' => 'string' ), 'has_token' => array( 'type' => 'boolean' ), 'scopes' => array( 'type' => 'array' ), 'created_at' => array( 'type' => 'string' ), ), ); } }