LoginSignup
27
27

More than 5 years have passed since last update.

Hubot に権限管理機能を追加し, より安全に使用する

Posted at

発端

Hubot に様々な機能を追加していると, 権限管理を行いたくなる.

  • deploy は, A さんと B さんのみが行える
  • Trello のチケット作成は, Chat Room に所属している者のみが行える

等々…

少し調べると, auth.coffee が見つかるが, Chat Room に制限が掛けられない.
Hubot は尻軽であるため, 招待されると, どのような Chat Room であっても顔を出してしまい, コマンドを入力されると, 素直に受け入れてしまう.

そこで, auth.coffee に Chat Room を判別する機能を追加しつつ, リファクタを試みる.

Source Code

auth.coffee
# Description:
#   Auth allows you to assign roles to users which can be used by other scripts
#   to restrict access to Hubot commands
#
# Dependencies:
#   None
#
# Configuration:
#   HUBOT_AUTH_ADMIN - A comma separate list of user IDs
#   HUBOT_AUTH_ROOM - A comma separate list of room IDs
#
# Commands:
#   hubot <user id> has <role> role - Assigns a role to a user
#   hubot <user id> doesn't have <role> role - Removes a role from a user
#   hubot what role does <user id> have - Find out what roles are assigned to a specific user
#   hubot who has admin role - Find out who's an admin and can assign roles
#
# Notes:
#   * Call the method: robot.auth.validUser(msg, '<role>')
#   * returns bool true or false
#
#   * the 'admin' role can only be assigned through the environment variable
#   * roles are all transformed to lower case
#
# Author:
#   IKUTA Masahito

module.exports = (robot) ->
  admin = process.env.HUBOT_AUTH_ADMIN or ''
  room = process.env.HUBOT_AUTH_ROOM or ''

  class Auth
    validUser: (msg, role) ->
      user = msg.message.user

      unless @inRoom(user)
        msg.reply "Can not execute in this room."
        return false

      unless @hasRole(user, role)
        msg.reply "#{user.name} has not the '#{role}' role."
        return false

      return true

    hasRole: (user, role) ->
      if @hasAdminRole(user.id)
        return true

      unless user.roles?
        return false

      return role in user.roles

    hasAdminRole: (id) ->
      return id.toString() in admin.toLowerCase().split(',')

    inRoom: (user) ->
      return user.room == room

  robot.auth = new Auth

  validParams = (msg, id, role) ->
    if not validIDParam(msg, id)
      return false

    if not validRoleParam(msg, role)
      return false

    return true

  validIDParam = (msg, id) ->
    if isNaN(id)
      msg.reply "ID is not integer."
      return false

    user = robot.brain.userForId(id)
    unless user?
      msg.reply "#{id} does not exist"
      return false

    if robot.auth.hasAdminRole(id)
      msg.reply "ID have admin role."
      return false

    return true

  validRoleParam = (msg, role) ->
    if role == 'admin'
      msg.reply "Sorry, the 'admin' role can only be defined in the HUBOT_AUTH_ADMIN env variable."
      return false

    return true

  robot.respond /auth help$/i, (msg) ->
    msg.reply '''
              <user id> has <role> role - Assigns a role to a user
              <user id> doesn't have <role> role - Removes a role from a user
              what role does <user id> have - Find out what roles are assigned to a specific user
              who has admin role - Find out who's an admin and can assign roles
              '''

  robot.respond /@?(.+) (has) (["'\w: -_]+) (role)/i, (msg) ->
    unless robot.auth.validUser(msg, 'auth')
      return

    name = msg.match[1].trim()
    newRole = msg.match[3].trim().toLowerCase()

    if name == 'who' and newRole == 'admin'
        msg.reply "The following people have the 'admin' role: #{admin.split(',')}"
        return

    id = parseInt(name)
    if not validParams(msg, id, newRole)
      return

    user = robot.brain.userForId(id)
    user.roles = user.roles or []
    if newRole in user.roles
      msg.reply "#{user.name} already has the '#{newRole}' role."
      return

    user.roles.push(newRole)
    msg.reply "Ok, #{user.name} has the '#{newRole}' role."

  robot.respond /@?(.+) (doesn't have|does not have) (["'\w: -_]+) (role)/i, (msg) ->
    unless robot.auth.validUser(msg, 'auth')
      return

    id = parseInt(msg.match[1].trim())
    newRole = msg.match[3].trim().toLowerCase()

    if not validParams(msg, id, newRole)
      return

    user = robot.brain.userForId(id)
    user.roles = (role for role in user.roles when role isnt newRole)
    msg.reply "Ok, #{user.name} doesn't have the '#{newRole}' role."

  robot.respond /(what role does|what roles does) @?(.+) (have)\?*$/i, (msg) ->
    id = parseInt(msg.match[2].trim())

    if not validIDParam(msg, id)
      return

    user = robot.brain.userForId(id)
    user.roles = user.roles or []

    if robot.auth.hasAdminRole(id)
      isAdmin = ' and is also an admin'
    else
      isAdmin = ''

    msg.reply "#{user.name} has the following roles: " + user.roles + isAdmin + "."
say.coffee
# Description:
#   Hubot always has a snappy comeback.
#
# Dependencies:
#   None
#
# Configuration:
#   None
#
# Commands:
#   hubot who am i - Display your name, id and room name.
#
# Notes:
#   None
#
# Author:
#   IKUTA Masahito

module.exports = (robot) ->
  robot.respond /who am i$/i, (msg) ->
    user = msg.message.user
    msg.reply """
              Name: #{user.name}
              ID: #{user.id}
              Room: #{user.room}
              """

使い方

say.coffee を設置し, 環境変数に設定する値を調べる.

Hubot> hubot who am i
Hubot>
Name: Shell
ID: 1
Room: Shell

値が得られたら, 環境変数を設定する.

bin/hubot-local
#!/bin/sh

npm install
export PATH="node_modules/.bin:node_modules/hubot/node_modules/.bin:$PATH"
export HUBOT_LOG_LEVEL="debug"

export HUBOT_AUTH_ADMIN="1"
export HUBOT_AUTH_ROOM="Shell"

exec node_modules/.bin/hubot "$@"

権限を設定する.

Hubot> hubot 2 has fabric role
Hubot> hubot 2 has trello role
Hubot> hubot what role does 2 have
Hubot> 2 has the following roles: trello,fabric.

各コマンドに権限を確認する処理を追加する.

trello.coffee
# ..snip..

module.exports = (robot) ->
  robot.respond /trello (.*)$/i, (msg) ->
    unless robot.auth.validUser(msg, 'trello')
      return

# ..snip..

これで, 少し安全に hubot を運用できる.

27
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
27