-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #47 from toadkicker/issue_39
Issue #39 - Add SNS Support
- Loading branch information
Showing
10 changed files
with
303 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
node_modules | ||
.DS_Store* | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Description: | ||
# List sns subscriptions | ||
# | ||
# Commands: | ||
# hubot sns list subscriptions | ||
|
||
module.exports = (robot) -> | ||
robot.respond /sns list subscriptions$/i, (msg) -> | ||
|
||
msg.send "Fetching ..." | ||
|
||
aws = require('../../aws.coffee').aws() | ||
sns = new aws.SNS() | ||
|
||
sns.listSubscriptions {}, (err, response) -> | ||
if err | ||
msg.send "Error: #{err}" | ||
else | ||
response.Subscriptions.forEach (subscription) -> | ||
labels = Object.keys(subscription) | ||
labels.forEach (label) -> | ||
msg.send(label + " " + subscription[label]) | ||
msg.send "____________________________________" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Description: | ||
# List sns subscriptions by topic | ||
# | ||
# Commands: | ||
# hubot sns list subscriptions in topic arn:aws... | ||
|
||
module.exports = (robot) -> | ||
|
||
robot.respond /sns list subscriptions in (.*)$/i, (msg) -> | ||
|
||
topic = msg.match[1] | ||
|
||
msg.send "Fetching subscriptions for " + topic | ||
|
||
aws = require('../../aws.coffee').aws() | ||
sns = new aws.SNS() | ||
|
||
sns.listSubscriptionsByTopic {TopicArn: topic}, (err, response) -> | ||
if err | ||
msg.send "Error: #{err}" | ||
else | ||
response.Subscriptions.forEach (subscription) -> | ||
labels = Object.keys(subscription) | ||
labels.forEach (label) -> | ||
msg.send(label + " " + subscription[label]) | ||
msg.send "____________________________________" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Description: | ||
# List sns topics | ||
# | ||
# Commands: | ||
# hubot sns list topics | ||
|
||
moment = require 'moment' | ||
tsv = require 'tsv' | ||
|
||
module.exports = (robot) -> | ||
robot.respond /sns list topics$/i, (msg) -> | ||
|
||
msg.send "Fetching ..." | ||
|
||
aws = require('../../aws.coffee').aws() | ||
sns = new aws.SNS() | ||
|
||
sns.listTopics {}, (err, response) -> | ||
if err | ||
msg.send "Error: #{err}" | ||
else | ||
response.Topics.forEach (topic) -> | ||
msg.send(topic.TopicArn) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Description: | ||
# Publish a message to SNS | ||
# | ||
# Commands: | ||
# hubot sns publish {message} to {message} | ||
|
||
module.exports = (robot) -> | ||
robot.respond /sns publish (.*) to (.*)/i, (msg) -> | ||
topicArn = msg.match[2] | ||
message = msg.match[1] | ||
subject = "Hubot SNS Published" | ||
msg.send('Publishing to ' + msg.match[2]) | ||
|
||
aws = require('../../aws.coffee').aws() | ||
sns = new aws.SNS() | ||
params = { | ||
TopicArn: topicArn, | ||
Message: message, | ||
Subject: subject | ||
} | ||
sns.publish params, (err, response) -> | ||
if err | ||
msg.reply "Error: #{err}" | ||
else | ||
msg.reply JSON.stringify(response) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# Description: | ||
# Allows for Hubot to recieve push notifications from SNS | ||
# | ||
# Dependencies: | ||
# None | ||
# | ||
# Configuration: | ||
# HUBOT_SNS_URL - the URL you want AWS SNS to POST messages | ||
# HUBOT_HIPCHAT_JID - the jabber id of the hubot | ||
# | ||
# Commands: | ||
# None | ||
# | ||
# URLs: | ||
# /hubot/sns | ||
# | ||
# Notes: | ||
# Use this snippet in your own scripts to send messages to the chat room from SNS: | ||
# | ||
# robot.on 'sns:notification', (message) -> | ||
# robot.messageRoom sns.channelID(message.subject), message.message | ||
# | ||
# Author: | ||
# mdouglass | ||
{inspect} = require 'util' | ||
{ verifySignature } = require './support/sns_message_verify' | ||
|
||
Options = | ||
url: process.env.HUBOT_SNS_URL or '/hubot/sns' | ||
|
||
class SNS | ||
constructor: (robot) -> | ||
@robot = robot | ||
|
||
@robot.router.post Options.url, (req, res) => @onMessage req, res | ||
|
||
onMessage: (req, res) -> | ||
chunks = [] | ||
|
||
req.on 'data', (chunk) -> | ||
chunks.push(chunk) | ||
|
||
req.on 'end', => | ||
req.body = JSON.parse(chunks.join('')) | ||
verifySignature req.body, (error) => | ||
if error | ||
@robot.logger.warning "#{error}\n#{inspect req.body}" | ||
@fail req, res | ||
else | ||
@process req, res | ||
|
||
fail: (req, res) -> | ||
res.writeHead(500) | ||
res.end('Internal Error') | ||
|
||
process: (req, res) -> | ||
res.writeHead(200) | ||
res.end('OK') | ||
|
||
@robot.logger.debug "SNS Message: #{inspect req.body}" | ||
if req.body.Type == 'SubscriptionConfirmation' | ||
@confirmSubscribe req.body | ||
else if req.body.Type == 'UnsubscribeConfirmation' | ||
@confirmUnsubscribe | ||
else if req.body.Type == 'Notification' | ||
@notify req.body | ||
|
||
confirmSubscribe: (msg) -> | ||
@robot.emit 'sns:subscribe:request', msg | ||
|
||
@robot.http(msg.SubscribeURL).get() (err, res, body) => | ||
if not err | ||
@robot.emit 'sns:subscribe:success', msg | ||
else | ||
@robot.emit 'sns:subscribe:failure', err | ||
return | ||
|
||
confirmUnsubscribe: (msg) -> | ||
@robot.emit 'sns:unsubscribe:request', msg | ||
@robot.emit 'sns:unsubscribe:success', msg | ||
|
||
notify: (msg) -> | ||
message = | ||
topic: msg.TopicArn.split(':').reverse()[0] | ||
topicArn: msg.TopicArn | ||
subject: msg.Subject | ||
message: msg.Message | ||
messageId: msg.MessageId | ||
|
||
@robot.emit 'sns:notification', message | ||
@robot.emit 'sns:notification:' + message.topic, message | ||
|
||
# Given a room name, create a fully qualified room JID | ||
# This is specific to hipchat. | ||
channelID: (name) -> | ||
if process.env.HUBOT_HIPCHAT_JID | ||
temp = name.toLowerCase().replace(/\s/g, "_") | ||
"#{process.env.HUBOT_HIPCHAT_JID.split("_")[0]}_#{temp}@conf.hipchat.com" | ||
else | ||
name.toLowerCase().replace(/\s/g, "_") | ||
|
||
module.exports = (robot) -> | ||
sns = new SNS robot | ||
robot.emit 'sns:ready', sns |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"default": "Hi! I learned how to get push notifications!", | ||
"http": "Hi! I learned how to get push notifications!" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
https = require('https') | ||
crypto = require('crypto') | ||
{inspect} = require('util') | ||
|
||
certificateCache = {} | ||
|
||
downloadCertificate = (url, cb) -> | ||
if url is undefined | ||
return cb new Error("Certificate URL not specified") | ||
|
||
if url in certificateCache | ||
return cb null, certificateCache[url] | ||
|
||
req = https.get url, (res) -> | ||
chunks = [] | ||
|
||
res.on 'data', (chunk) -> | ||
chunks.push(chunk) | ||
res.on 'end', -> | ||
certificateCache[url] = chunks.join('') | ||
return cb null, certificateCache[url] | ||
|
||
req.on 'error', (error) -> | ||
return cb new Error('Certificate download failed: ' + error) | ||
|
||
signatureStringOrder = | ||
'Notification': ['Message', 'MessageId', 'Subject', 'Timestamp', 'TopicArn', 'Type'], | ||
'SubscriptionConfirmation': ['Message', 'MessageId', 'SubscribeURL', 'Timestamp', 'Token', 'TopicArn', 'Type'], | ||
'UnsubscribeConfirmation': ['Message', 'MessageId', 'SubscribeURL', 'Timestamp', 'Token', 'TopicArn', 'Type'] | ||
|
||
createSignatureString = (msg) -> | ||
chunks = [] | ||
for field in signatureStringOrder[msg.Type] | ||
if field of msg | ||
chunks.push field | ||
chunks.push msg[field] | ||
return chunks.join('\n') + '\n' | ||
|
||
verifySignature = (msg, cb) -> | ||
if msg.SignatureVersion isnt '1' | ||
return cb new Error("SignatureVersion '#{msg.SignatureVersion}' not supported.") | ||
|
||
downloadCertificate msg.SigningCertURL, (error, pem) -> | ||
if error | ||
return cb error | ||
|
||
signatureString = createSignatureString msg | ||
|
||
try | ||
verifier = crypto.createVerify('RSA-SHA1') | ||
verifier.update(signatureString, 'utf8') | ||
if not verifier.verify(pem, msg.Signature, 'base64') | ||
return cb new Error('Signature verification failed') | ||
catch error | ||
return cb new Error('Signature verification failed: ' + error) | ||
|
||
return cb null | ||
|
||
exports.verifySignature = verifySignature |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters