Develop:WebService

Matthias Hecker edited this page Mar 9, 2015 · 3 revisions

The web service is a built-in http server that can be used to remotely control or query the bot. It has the built-in ability to dispatch commands and retreive the plaintext response exactly like a IRC user would. Any plugin can easily add their own url routes to respond to requests.

  • uses the ruby built-in WEBrick server implementation
  • support for ssl, which is recommended if you expose the server to the internet
  • easy to use interface for plugins similar to how irc command mapping work
  • integrates in the security architecture of rbot, requesting HTTP authentication if necessary
  • replaces the DRb remote interface with a more secure and portable solution

Configuration

Web service configuration values can be modified using the irc interface.

Name Type/Default Description
webservice.autostart Boolean (false) Whether the web service should be started automatically
webservice.port Integer (7260) Port on which the web service will listen
webservice.host String (127.0.0.1) Host the web service will bind on
webservice.ssl Boolean (false) Whether the web server should use SSL
webservice.ssl_key String (~/.rbot/wskey.pem) Private key file to use for SSL
webservice.ssl_cert String (~/.rbot/wscert.pem) Certificate file to use for SSL
webservice.allow_dispatch Boolean (true) If you want to allow dispatching irc commands

As you can see the web service does not start by default, you can start/stop it manually or set autostart:

<user> bot: webservice start
<user> bot: webservice stop
<user> bot: config set webservice.autostart true

If you want to use SSL for the web service you need to create a SSL certificate, to create a self signed certificate using openssl you can use:

openssl req -x509 -newkey rsa:2048 -keyout ~/.rbot/wskey.pem -out ~/.rbot/wscert.pem -days 2000 -nodes

You can also use a web server like nginx as a frontend server.

Authentication

Requests are handled as the default bot user (without special permissions) you can send a HTTP authentication header to login as a bot user.

IRC Dispatch

This allows you to execute a bot command just like a IRC user would, the configuration value webservice.allow_dispatch can be used to deactivate this feature.

POST /dispatch

Query Parameter Type
command string IRC command (like in a private message to the bot)

This will respond with a text/plain response or if a Accept: application/json header was sent in the request a application/json json encoded response:

Response Object Type
reply array Array with strings the bot said in response to the command
% curl -d 'command=ping' http://127.0.0.1:7268/dispatch
pong
% curl -d 'command=help urban' http://127.0.0.1:7268/dispatch
urban [word] [n]: give the [n]th definition of [word] from urbandictionary.com. urbanday: give the word-of-the-day at urban
% curl -d 'command=urban matrix' http://127.0.0.1:7268/dispatch
matrix (1/7): computer-generated dream world built to keep us under control in order to change a human being into a battery. NO ONE CAN BE TOLD WHAT THE MATRIX IS, YOU HAVE TO SEE IT FOR YOURSELF. k.
% curl -d 'command=imdb the matrix' http://127.0.0.1:7268/dispatch 
Matrix (USA | Australia, 1999) : http://www.imdb.com/title/tt0133093/
Ratings: 8.7/10 (969,878 voters). Genre: Action/Sci-Fi. Plot: A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers

Plugin Development

Plugins can register url "routes" they want to handle, right now the template syntax for this is not very featureful just supporting arguments /method/:<argument> that match against the url path. Here is a minimal plugin that handles the url /hello:

class WSPlugin < Plugin
  include WebPlugin

  def hello(m, params)
    'World!'
  end
end

plugin = WSPlugin.new
plugin.web_map '/hello',
  :action => :hello
  • Plugins need to include the WebPlugin mixin to use the web_map method to register handled urls.
  • The hello action method needs to be declared as a option parameter in web_map.
  • The hello method returns a string that is then encoded as json and sent as a response.

Here is a another example that is using a url path parameter:

class WSPlugin < Plugin
  include WebPlugin

  def hello(m, params)
    name = params[:name]
    m.send_html('<h1>Hello, %s!</h1>' % [name])
  end
end

plugin = WSPlugin.new
plugin.web_map '/hello/:name',
  :action => :hello,
  :method => 'GET'
  • :<argument-name> is used as a optional argument and exposed in the params hash with <argument-name> as a key.
  • The :method mapping option might be used to constrain a route to a HTTP method.
  • The send_html helper method takes a body and optionally a HTTP status code, that is then sent as a text/html response. Other helpers include send_json(body, [status]), send_plaintext(body, [status]), send_response(body, [status], [content type]).
  • You can also directly access the WEBrick request and response objects in m.req (HTTPRequest) and m.res (HTTPResponse).
% curl http://127.0.0.1:7268/hello/world
<h1>Hello, world!</h1>

Authentication / Security

If you want to restrict access to the route, you can do something like this: (This works similar to irc command mappings)

class WSPlugin < Plugin
  include WebPlugin

  def hello(m, params)
    'World!'
  end
end

plugin = WSPlugin.new
plugin.web_map '/hello',
  :action => :hello
plugin.web_map '/restricted',
  :action => :hello,
  :auth_path => 'private'

plugin.default_auth('private', false)
  • :auth_path sets a specific auth path for this command.
  • default_auth uses this auth path to restrict access to this action.

If you try to access the route:

% curl -i http://127.0.0.1:7268/restricted
HTTP/1.1 401 Unauthorized 
Server: RBot Web Service (http://ruby-rbot.org/)
Content-Type: text/plain
Date: Mon, 12 Jan 2015 10:48:28 GMT
Content-Length: 24
Connection: Keep-Alive

Authentication Required!

You might use the default owner user to access this, but its not recommended, instead you should create a new bot user and allow it to access the route (in irc):

<user> bot: user create hello swordfish
<user> bot: permissions set +ws::private for hello

In this case "ws::private", ws is determined by the name of the plugin, this can be overwritten. Afterwards you can use basic HTTP auth to login and use the restricted command:

% curl -u hello:swordfish -i http://127.0.0.1:7268/restricted 
HTTP/1.1 200 OK 
Server: RBot Web Service (http://ruby-rbot.org/)
Content-Type: application/json
Date: Mon, 12 Jan 2015 11:01:25 GMT
Content-Length: 8
Connection: Keep-Alive

"World!"

Our original example used default_auth to specify a single route that is restricted, but you can also do it the other way around, you specify all routes as restricted and allow the ones you want to make unrestricted:

class WSPlugin < Plugin
  include WebPlugin

  def hello(m, params)
    'World!'
  end
end

plugin = WSPlugin.new
plugin.web_map '/hello',
  :action => :hello,
  :auth_path => 'public'
plugin.web_map '/restricted',
  :action => :hello

plugin.default_auth('*', false)
plugin.default_auth('public', true)