Path: blob/master/spec/integration/msfmcpd/messagepack_reauth_flow_spec.rb
70330 views
# frozen_string_literal: true12require 'msf/core/mcp'3require 'webmock/rspec'45RSpec.describe 'MessagePack Re-Authentication Flow Integration' do6before(:all) do7WebMock.disable_net_connect!(allow_localhost: false)8end910after(:all) do11WebMock.allow_net_connect!12end1314let(:host) { 'localhost' }15let(:port) { 55553 }16let(:endpoint) { '/api/' }17let(:api_url) { "https://#{host}:#{port}#{endpoint}" }18let(:user) { 'test_user' }19let(:password) { 'test_password' }2021describe 'Automatic Re-Authentication on Token Expiry' do22it 're-authenticates and retries when API call returns 401' do23call_count = 02425# Stub all POST requests and dispatch based on body content26stub_request(:post, api_url).to_return do |request|27body = MessagePack.unpack(request.body)28call_count += 12930case call_count31when 132# Initial authentication succeeds33expect(body[0]).to eq('auth.login')34{35status: 200,36body: { 'result' => 'success', 'token' => 'initial_token' }.to_msgpack,37headers: { 'Content-Type' => 'binary/message-pack' }38}39when 240# First API call returns 401 (token expired)41expect(body[0]).to eq('module.search')42expect(body[1]).to eq('initial_token')43{44status: 401,45body: { 'error_message' => 'Token expired' }.to_msgpack,46headers: { 'Content-Type' => 'binary/message-pack' }47}48when 349# Re-authentication succeeds with new token50expect(body[0]).to eq('auth.login')51{52status: 200,53body: { 'result' => 'success', 'token' => 'refreshed_token' }.to_msgpack,54headers: { 'Content-Type' => 'binary/message-pack' }55}56when 457# Retry with new token succeeds58expect(body[0]).to eq('module.search')59expect(body[1]).to eq('refreshed_token')60{61status: 200,62body: [{ 'fullname' => 'exploit/test', 'type' => 'exploit', 'name' => 'test' }].to_msgpack,63headers: { 'Content-Type' => 'binary/message-pack' }64}65else66raise "Unexpected request ##{call_count}: #{body.inspect}"67end68end6970client = Msf::MCP::Metasploit::MessagePackClient.new(71host: host,72port: port,73endpoint: endpoint74)75client.authenticate(user, password)7677# This call should trigger: 401 → re-auth → retry → success78result = client.search_modules('smb')7980expect(result).to be_an(Array)81expect(result.first['fullname']).to eq('exploit/test')82expect(client.instance_variable_get(:@token)).to eq('refreshed_token')83expect(call_count).to eq(4)84end8586it 'gives up after max retries when re-auth succeeds but API keeps failing' do87call_count = 08889stub_request(:post, api_url).to_return do |request|90body = MessagePack.unpack(request.body)91call_count += 19293if body[0] == 'auth.login'94{95status: 200,96body: { 'result' => 'success', 'token' => "token_#{call_count}" }.to_msgpack,97headers: { 'Content-Type' => 'binary/message-pack' }98}99else100# API calls always return 401101{102status: 401,103body: { 'error_message' => 'Token invalid' }.to_msgpack,104headers: { 'Content-Type' => 'binary/message-pack' }105}106end107end108109client = Msf::MCP::Metasploit::MessagePackClient.new(110host: host,111port: port,112endpoint: endpoint113)114client.authenticate(user, password)115116# Should exhaust retries (max_retries=2) and re-raise117expect {118client.search_modules('smb')119}.to raise_error(Msf::MCP::Metasploit::AuthenticationError)120end121122it 'propagates re-auth failure through the tool layer as an error response' do123stub_request(:post, api_url)124.with(body: ['auth.login', user, password].to_msgpack)125.to_return(126status: 200,127body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,128headers: { 'Content-Type' => 'binary/message-pack' }129)130131# All subsequent requests return 401132stub_request(:post, api_url)133.with { |req| MessagePack.unpack(req.body)[0] != 'auth.login' }134.to_return(135status: 401,136body: { 'error_message' => 'Token invalid' }.to_msgpack,137headers: { 'Content-Type' => 'binary/message-pack' }138)139140client = Msf::MCP::Metasploit::MessagePackClient.new(141host: host,142port: port,143endpoint: endpoint144)145client.authenticate(user, password)146147limiter = Msf::MCP::Security::RateLimiter.new(requests_per_minute: 60)148server_context = { msf_client: client, rate_limiter: limiter }149150result = Msf::MCP::Tools::SearchModules.call(query: 'smb', server_context: server_context)151152expect(result.error?).to be true153expect(result.content.first[:text]).to include('Authentication failed')154end155end156end157158159