slack-ruby-bot-authorization

An authorization framework for slack-ruby-bot

How to Use It

Many bots are created by subclassing SlackRubyBot::Bot or SlackRubyBot::Commands::Base and adding commands (or using the cool new MVC features in slack-ruby-bot). This framework works similarly by allowing inclusion of a module into the subclass to add some authorization features.

class MyBot < SlackRubyBot::Bot
  extend SlackRubyBotAuthorization

end

Including the module adds several important methods to the subclass that can be used to register roles, users, and commands. Additionally, we can customize the "denial" behavior when a user runs a command for which they are not authorized.

class MyBot < SlackRubyBot::Bot
  extend SlackRubyBotAuthorization

  add_default_denial_handler(Proc.new do |client, data, match|
    # Called when a command is not permitted to user/role and a specific
    # denial Proc hasn't already been registered
    client.say(channel: data.channel, text: "Sorry, but you have been denied!")
  end
  )

  add_users_to_role('admin','abc', 'def', 'ghi', 'jkl')

  role = add_commands_to_role('admin','update inventory', 'remove user', 'add user', 'run report')

  role.add_denial_handler(
    Proc.new do |client, data, match|
        client.say(...)
        # returning true means the default_denial should also run
        # return false would indicate only this proc should run and skip
        # the default
        true
      end
  )

  remove_users_from_role('admin', 'abc')
  remove_commands_from_role('admin', 'run report')
  remove_denial_handler_from_command('update inventory') # default, if set, will still run

  admin_role = new_role('admin') do |role|
    role.enable_slack_builtin_commands('hi') # defaults to all unless specifically listed
    role.add_users('user1', 'user2', 'user3')
    role.add_commands('cmd1', 'cmd2', 'cmd3', 'cmd4')
    role.add_default_denial_handler(proc { |a, b, c| nil } )
  end

  admin_role.remove_users('user1', 'user2')
  admin_role.remove_commands('cmd1')
  command = admin_role.find_command('cmd1')
  command.add_denial_handler(
    proc { |a, b, c| p a, b, c; return false }
  )
end

# Generate an array of commands that are in a 
# `SlackRubyBot::Commands::Base` subclass but
# that have not been authorized via the auth framework
missing = MyBot.unauthorized_commands
unless missing.empty?
  raise 'Some commands have not been added to any role! ' + missing.inspect
end

# Generate an array of commands that were authorized
# in the framework but do NOT exist in the
# bot subclasses.
missing = MyBot.missing_commands
unless missing.empty?
  raise 'Some authorized commands do not exist in the bot! ' + missing.inspect
end

In situations where multiple strings can trigger a command, each string should be added to the role. For example, a command may respond to three different strings such as 'add', 'create new', and 'append to collection'. While they all execute the same business logic, from the perspective of the authorization framework they are different commands. Permission to run all 3 should be granted to the roles that need to execute any of them.

Adding a denial proc via add_denial_handler_to_command multiple times results in the last operation overwriting any previous operations. The default_denial proc can still be scheduled to run if the command-specific proc returns true. If it returns false, the default denial proc is skipped.

Defaults

The system works off the assumption that "everything is prohibited unless expressly allowed." That is, it's a whitelist. Only those users (and roles) that are specifically registered to run commands may run them. All others attempting to run those commands will be denied. This is the most secure approach and is highly recommended.

Note that this behavior extends to builtin commands such as 'about', 'help', and 'hi.' If not explicitly allowed, these commands will be denied.

How to Subvert the Whitelist Behavior

Programmers who are writing their own code can obviously overrule the defaults. The included module contains a method named permitted? which overrides the default method of the same name in SlackRubyBot::Commands::Base. This method may also be overridden by the subclass which would wipe out the whitelist behavior.

This is not recommended.

class MyBot < SlackRubyBot::Bot
  include SlackRubyBotAuthorization

  # Permit any user to run any command
  def permitted?(*args)
    return true
  end
end

Warning

The API is subject to change without warning until version 1.0 is released. This project will follow semantic versioning.

(c) Copyright 2017 Stax Logistics LLC