Skip to content
Matthias Hecker edited this page Mar 9, 2015 · 6 revisions

Rbot features an extremely powerful and flexible authentication and permission system that allows users of the bot to easily setup very specific permissions for everything. What follows is a detailed description of said system for users and developers.

In the new auth system, we have three objects: Commands, PermissionSets and BotUsers.

  • Commands
    • Commands are defined in BotModules (which can be CoreModules such as Core, Auth, and Config, or Plugins such as Math, Rss or Quiz) with the map and register methods
    • each Command has a path in the form foo::bar::baz::quuz::quark::donuts (ridiculously long example)
    • the default path for Command doit defined in BotModule botmodule is botmodule::doit
    • the default path for a Command is changed by the :auth_path keyword in the map method (see below)
    • obviously, nothing prevents the method associated with the Command from checking against an arbitrary path
  • PermissionSets
    • PermissionSets are Hashes that associate a Command path to a boolean value (true or false)
    • PermissionSets need not specify a value for any possible Command path
    • the special Command path * means "any command"
  • BotUsers
    • BotUsers are how the bot views Irc::Users wrt its Auth system
    • each Irc::User has an associated BotUser, the default being DefaultBotUser; in the future, we may add other 'unlogged' users such as NickservBotUser (known to nickserv) or whatever
    • a user can log in to the bot, in which case (s)he will be associated with the BotUser (s)he logged in as; see the comments below for a discussion on the usage of hostmasks
    • each BotUser has a permissions Hash whose keys are Channel names and whose values are PermissionSets; there are two special channel names, * to indicate any channel and ? to indicate private messages
    • the only exception is the special BotOwner user (owner) which always returns true when asked for permissions (this could be obtained by associating the PermissionSet * => true to channel *, but we cut a corner and just redefined the underlying method): so whoever is logged in as BotOwner can do as (s)he pleases.

Logging in and managing users

To let the bot recognize you as a given BotUser username, you need to login with the command

login username password

issued in private.

As an example, consider the BotOwner, whose username is owner; if the master password is foobar, the bot owner would login with

login owner foobar

It is always possible to ask the bot who it thinks you are by asking

rbot: whoami

If your BotUser has the login-by-mask feature enabled, and your IRC user mask matches a netmask known to the BotUser, you can login by just giving the command

login username

(without specifying the password), in public or in private.

If your BotUser has the autologin feature enabled, and your IRC user mask matches a netmask known to the BotUser, there is no need to login at all: you will be logged in automatically.

New BotUsers can be created with the command

user create username password

where username is a new username and password is the password for this new BotUser. You can omit the password parameter, in which case a random password will be created.

You can rename a BotUser with

user rename oldname newname

You can create a new BotUser based on an existing one (for example to copy the permissions) with

user copy oldname newname

BotUsers can be destroyed in a two step process:

user destroy username

queues BotUser username for destruction. You can cancel the destruction with

user cancel destroy username

or confirm it with

user confirm destroy username

You can have the bot tell an IRC user the password for a given BotUser with the command

user tell nick the password for username

where nick is the nick of an IRC user and username is the username of the BotUser.

Each BotUser has four properties: login-by-mask, autologin, a list of known netmasks and a list of permissions. You can enable/disable the login-by-mask and autologin with commands such as

user enable login-by-mask
user disable autologin

The password is changed with

user set password newpass

where newpass is the new password.

You can add or delete known netmasks with

user add netmask some*!net?@*mask
user rm netmask some*!net?@*mask

You can show the current value of all of these properties with

user show all [for username]
user show someproperty [for username]

where someproperty is any of login-by-mask, autologin, netmasks, password. If no username is specified, the one shown is the one you are currently logged in as.

All properties can be reset to their default value with

user reset someproperty

except the password: resetting the password just creates a new random one.

Managing permissions

Simple Syntax

The bot comes with two commands that allow you to assign or take back the permission to run given commands. These are:

allow someuser to do <some sample command> [in #somechannel]

and the converse

deny someuser from doing <some sample command> [in #somechannel]

In both commands, someuser is the username of the user you want to give permission to (or take permission from), and <some sample command> is a sample command like imdb The Matrix. An optional channel specification can be given. Example usage:

allow someuser to do alias command => expansion

will give someuser the permissions necessary to run the alias command.

These methods are comfortable to use to make basic permission management. More sophisticated management can be done, but this require a more thorough understanding of the underlying permission structure and the lower level commands necessary to manage it.

How it works

In this document we will use the notation

user -> #chan -> foo::bar::baz::doit

to denote a permission chain: it checks for the existence of the key foo::bar::baz::doit in the PermissionSet for channel #chan for BotUser user, and it returns true or false if all elements in the chain exist, and nil otherwise (e.g. if user does not have the key #chan in his permissions Hash, or if there is no foo::bar::baz::doit in the associated PermissionSet.

The actual syntax used to set the permissions will be explained later on.

BotUser user issues command doit (with path foo::bar::baz::doit) on channel #chan. His PermissionSet for channel #chan are checked (in order) for the keys:

  1. foo::bar::baz::doit
  2. foo::bar::baz
  3. foo::bar
  4. foo

If none of these keys are found in the PermissionSet for channel #chan, the same checks are ran against channel *. If nothing is found, we check the PermissionSet for channel #chan for user DefaultBotUser, and if necessary those for channel * for user DefaultBotUser. An exception is raised if no permission (neither true nor false) is found at all.

The permission chain checked for are thus:

user -> #chan -> foo::bar::baz::doit
user -> #chan -> foo::bar::baz
user -> #chan -> foo::bar
user -> #chan -> foo
user -> #chan -> *
user -> * -> foo::bar::baz::doit
user -> * -> foo::bar::baz:
user -> * -> foo::bar
user -> * -> foo
user -> * -> *
default -> #chan -> foo::bar::baz::doit
default -> #chan -> foo::bar::baz
default -> #chan -> foo::bar
default -> #chan -> foo
default -> #chan -> *
default -> * -> foo::bar::baz::doit
default -> * -> foo::bar::baz
default -> * -> foo::bar
default -> * -> foo
default -> * -> *

and the value returned by the first one which doesn't give nil determines if the BotUser? is allowed the action or not; if nothing is returned an exception is raised on the assumption that the bot isn't configured properly.

How to set it up

This mechanism based on fallbacks means that BotUser management is very easy while being extremely flexible.

  • default permission for all commands on all channel are given by setting the permission for default -> * -> *
  • a whole (sub)group of commands can be enabled/disabled for a specific user or for all users by setting the permission for user -> * -> path::to::group (or default -> * -> path::to::group)
  • more specific permissions take precedence over less specific ones, so it is possible to enable/disable a whole (sub)group of commands except for a specific command or (sub)subgroup.

Example

Assume that we have the following permission chains

user -> #chan -> core::config::show::status : true
user -> * -> core::config::show             : false
default -> * -> core::config::show        : true
default -> * -> core                      : false
default -> * -> *                         : true

This means that all users are allowed all commands except for the core ones; they are however allowed commands in the core::config::show namespace (e.g. status or version. Moreover, user is not allowed commands in the core::config::show namespace except for the command status on channel #chan

BotModule author vs bot owner

It is up to the botmodule author to choose how fine grained the control can be.

As an example, let's look at the Rss plugin, which makes available the following commands: show, list, watched, add, delete, replace, watch, unwatch, rewatch (and synonyms). All these commands are prefixed by rss, so they are called with something like

<tango_> testbot: rss list

As the both author, I may want to group the commands this way:

  • rss::list: show, list, watched
  • rss::edit: add, delete, replace, watch, unwatch, rewatch

However, this means that if the bot owner wants to grant editing capabilities to someone, limited to the watch features, he would have to set:

default -> * -> rss::edit : false
user -> * -> rss::edit::watch : true
user -> * -> rss::edit::unwatch : true
user -> * -> rss::edit::rewatch : true

which is a PITN. So we may want to split the commands the edit the list from the commands that edit the watches:

  • rss::list: show, list, watched
  • rss::editlist: add, delete, replace
  • rss::editwatch: watch, unwatch, rewatch

However, this can still be improved, by grouping all editing commangs together, and still keeping the list editing and watch editing commands in separate groups:

  • rss::list: show, list, watched
  • rss::edit::list: add, delete, replace
  • rss::edit::watch: watch, unwatch, rewatch

since this allows the bot owner to provide control as he wishes: he can enable/disable (as needed or wanted) rss as a whole with finer control on all editing actions by chaning the permissions for rss::edit, or just for rss::edit::watch or rss::edit::list.

Remember, as the botmodule author you have to provide as fine a control as possible. It's then up to the bot owners to choose how much of this control to exploit in setting permissions: while most of them will not make use of the whole depth, there may be bot owners that want to set the accessibility to very fine grained levels. If the botmodule author doesn't provide the necessary granularity, the bot owner will find himself forced to hand-edit the plugin.

BotModule authors can also set the default permission for any of the command paths, using the default_auth method. For example, the defaults for the Rss plugin would be:

default -> * -> rss::edit : false

which would be set with the command

plugin.default_auth('rss::edit', false)

When writing a plugin, please use safe defaults. Set them with the following assumptions:

  • the BotOwner will be able to do everything, so you don't have to worry about setting thins to true for him
  • the DefaultBotUser will have access to everything except for the commands which get disabled So most default_auth commands will just set the permission for potentially dangerous or annoying commands to false

Syntax

On IRC

Permissions can be changed on IRC with the following command syntax:

permissions set <perm>... [where] for <user>

where

  • <user> is the BotUser you want to set the permissions for; you can use everyone or all to associate the permission changes to the default user
  • [where] is an optional location, with the sintax on #chan or in private
  • <perm> is a permission in the form +foo::bar::baz to allow foo::bar::baz to allow it or -foo::bar::baz to forbid it; more than a single permission can be specified at the same time, so you can do things such as
permissions set -foo +foo::bar -quuz for guy_debord

If you want to reset some permissions to their default, you can use

permissions reset <perm>... [where] for <user>

where [where] and <user> follow the same syntax as above, but the permission(s) <perm> are specified without the initial + or - sign.

Resetting the permissions for the DefaultBotUser brings them back to the value suggested by the BotModule authors.

For BotModule authors

The highest burden of the new auth module lies on the BotModule authors. Of course, simple plugins will not need any of this configurations, but someone who wants to deploy a more complex plugin will need to understand the new auth framework.

Introduction

BotModule authors create new bot commands with the register method or with the map method.

When register is used, the registered command will have command path given by the plugin name followed by the registered command, if the registered command is different from the plugin name.

class TestPlugin < Plugin
  def privmsg(m)
    m.reply "Hello!"
  end
end

plugin = TestPlugin.new
plugin.register('test')  # command path: test
plugin.register('test2') # command path: test::test2

When map is used, the same principle is followed, except that if the first word is the same as the plugin name, the second word will be used (if possible). For example, the Rss plugin provides the rss add command to add an rss feed to the list of known feeds:

plugin.map 'rss add :handle :url :type',
  :action => 'add_rss',
  :defaults => {:type => nil}

and the default command path for this would be rss::add

Backwards compatibility

BotModule authors need to specify command paths in two places:

in the :auth key for the map method, which sets the command path to be checked to see if a user can run the command for the default_auth method, to set the default permission for the default user (if necessary) By default the old system would check permission for the rss add command against the 'rss' level (if it existed), since the command starts with the word rss; the new mechanism would check it by default against rss::add.

If the author didn't like this, in the old system (s)he could have chosen to create a new level ('rss_add', for example) to check against, but this wouldn't have had sensible defaults, or (s)he could have chosen to check the availability of the command against and existing level ('config', for example) which would have had a reasonable default on most installations. In the new system, the BotModule author can choose a proper path for the command (for example rss::edit::list::add, as was discussed above)

To my knowledge, there is no way to automagically convert old :auth specifications to ones that make sense in the new system, regardless of the new syntax chosen for :auth. Since it is desirable that the old plugins keep working in the new system, I propose to 'deprecate' the :auth key: when it's encountered, it will be ignored (with a warning). The new :auth_path key can be used to specifiy different command paths.

Syntax

The most common thing that wants to be done is add path specifiers between the first and last path in the component. In this case, the syntax would be :auth_path => 'new::path::components'. For example, to make rss add check for rss::edit::list::add, we would define the command as follows:

plugin.map 'rss add :handle :url :type',
  :action => 'add_rss',
  :defaults => {:type => nil},
  :auth_path => 'edit::list'

Just that. The first and last component will be added automatically.

The same thing is true for the default_auth method:

plugin.default_auth 'edit::list', false

would set the default permission to rss::edit::list to false.

Advanced usage

In the rare case that you want the new path to be the actual path you specify, you can prepend an exclamation mark (!) to the path to prevent the first default component to be added or append it to prevent the last component to be added.

Examples:

plugin.map 'rss add :handle :url :type',
  :action => 'add_rss',
  :defaults => {:type => nil},
  :auth_path => 'edit::list' # Would check for rss::edit::list::add

plugin.map 'rss add :handle :url :type',
  :action => 'add_rss',
  :defaults => {:type => nil},
  :auth_path => '!edit::list' # Would check for edit::list::add

plugin.map 'rss add :handle :url :type',
  :action => 'add_rss',
  :defaults => {:type => nil},
  :auth_path => 'edit::list!' # Would check for rss::edit::list

plugin.map 'rss add :handle :url :type',
  :action => 'add_rss',
  :defaults => {:type => nil},
  :auth_path => '!edit::list!' # Would check for edit::list

If the argument to :auth_path begins with a colon (:), the new path will be appended to one chosen by default. If the argument ends with a colon, the next word in the map specification will be appended to the path.

For the examples, assume now that the Rss plugin was called Feed, but the syntax hadn't changed:

plugin.map 'rss add :handle :url :type',
  :action => 'add_rss',
  :defaults => {:type => nil} # Would check for feed::rss

plugin.map 'rss add :handle :url :type',
  :action => 'add_rss',
  :defaults => {:type => nil},
  :auth_path => ':edit::list' # Would check for feed::rss::edit::list

plugin.map 'rss add :handle :url :type',
  :action => 'add_rss',
  :defaults => {:type => nil},
  :auth_path => 'edit::list:' # Would check for feed::edit::list::rss::add

plugin.map 'rss add :handle :url :type',
  :action => 'add_rss',
  :defaults => {:type => nil},
  :auth_path => ':edit::list:' # Would check for feed::rss::edit::list::add

The exclamation and colon extensions are not available when specifying the command paths for the default_auth method.

Clone this wiki locally