This repository has been archived by the owner on Sep 6, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
/
approval.coffee
109 lines (93 loc) · 4.29 KB
/
approval.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# Description
# Middleware for managing peer approvals of Hubot commands
#
# Listener Options:
# approval:
# group: String - (Required) name of approvers group
# peer: bool - (Optional, default false)
# if true, peer approvals are required;
# if false and user in approvers group, auto-approve
#
# Commands:
# hubot approve MAGIC_WORD - Approve a command
#
# Configuration:
# HUBOT_APPROVAL_TIMEOUT - Number of minutes before command expires (default: 1)
#
# Dependencies:
# User object is expected to have a 'groups' function (async) will callback
# with a list of groups the user is in
#
# Notes:
# robot.respond /approval test$/, { id: 'approval.test', approval: { group: 'admin' } }, (msg) ->
# msg.reply 'Approval test successful!'
#
# robot.respond /approval test peer$/, { id: 'approval.test-peer', approval: { group: 'admin', peer: true } }, (msg) ->
# msg.reply 'Approval test successful!'
#
# Author:
# Michael Ansel <mansel@box.com>
fs = require 'fs'
path = require 'path'
APPROVAL_TIMEOUT_MS = (process.env.HUBOT_APPROVAL_TIMEOUT or 1) * 60 * 1000 # minutes to ms
WORDS = fs.readFileSync(path.resolve(__dirname, 'words.txt')).toString().split('\n')
class AuthApproval
constructor: (@robot) ->
@approvals = {}
@robot.listenerMiddleware (context, next, done) =>
return next() unless context.listener.options.approval?
if not context.response.message.user.groups?
context.response.reply "Sorry, approvals are broken because your robot is misconfigured. Please ensure you have listener middleware that is adding a 'groups' method onto the User object. Failing closed..."
@robot.logger.error "Unable to find a 'group' function on the User object: #{Object.keys context.response.message.user}"
return done()
context.response.message.user.groups (userGroups) =>
# Unless peer approval is required, auto-approve if user is in the
# approvers group
if not context.listener.options.approval.peer and
context.listener.options.approval.group in userGroups
return next()
# Get a unique magic word (not already in use)
magic_word = @generateMagicWord exclude: Object.keys(@approvals)
@approvals[magic_word] =
context: context
next: next
done: done
context.response.reply "I need approval for that from someone in '#{context.listener.options.approval.group}'. In order to approve, say '#{@robot.name} approve #{magic_word}'."
# Clean up if not approved within the time limit
setTimeout (=> delete @approvals[magic_word]), APPROVAL_TIMEOUT_MS
@robot.respond /approve (.+)$/, (msg) =>
magic_word = msg.match[1]
attempt = @approvals[magic_word]
if not attempt?
msg.reply "I don't know what you're talking about..."
return
msg.message.user.groups (userGroups) =>
if attempt.context.response.message.user is msg.message.user
msg.reply "Oh, come on! Self-approving isn't allowed!"
else if attempt.context.listener.options.approval.group in userGroups
msg.reply "Approved! Executing '#{attempt.context.response.match[0]}' for #{attempt.context.response.message.user.name}"
delete @approvals[magic_word]
attempt.next(attempt.done)
else
msg.reply "Sorry, only someone in '#{attempt.context.listener.options.approval.group}' can approve this command!"
@robot.respond /reject (.+)$/, (msg) =>
magic_word = msg.match[1]
attempt = @approvals[magic_word]
if not attempt?
msg.reply "I don't know what you're talking about..."
return
msg.message.user.groups (userGroups) =>
if attempt.context.response.message.user is msg.message.user or
attempt.context.listener.options.approval.group in userGroups
msg.reply "Oh well... maybe next time!"
delete @approvals[magic_word]
else
msg.reply "Oh hush. You can't do that!"
generateMagicWord: (options) ->
exclude_list = options.exclude or []
magic_word = WORDS[ Math.floor(Math.random() * WORDS.length) ]
# Keep trying
until magic_word not in exclude_list
magic_word = WORDS[ Math.floor(Math.random() * WORDS.length) ]
magic_word
module.exports = (robot) -> new AuthApproval(robot)