Skip to content
This repository has been archived by the owner on Sep 29, 2021. It is now read-only.

Dev changes #13

Merged
merged 4 commits into from
Sep 2, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ You must pass the following environment variables to hubot
* `HUBOT_SYMPHONY_PRIVATE_KEY` set to the location of your bot account .pem private key file
* `HUBOT_SYMPHONY_PASSPHRASE` set to the passphrase associated with your bot account private key

There is also an optional argument which should be used if you are running an on-premise key manager
* `HUBOT_SYMPHONY_KM_HOST` set to the url of your key manager without the https:// prefix

These arguments are passed through to the NodeJs request module as described [here](https://github.com/request/request#tlsssl-protocol).

If you want to send a rich message you can call send with an Object instead of a String
Expand All @@ -46,6 +49,13 @@ npm install hubot-symphony
npm run diagnostic -- --publicKey [key1.pem] --privateKey [key2.pem] --passphrase [changeit] --host [host.symphony.com]
```

If you are running an on-premise key manager you can add an optional fifth argument

```
npm install hubot-symphony
npm run diagnostic -- --publicKey [key1.pem] --privateKey [key2.pem] --passphrase [changeit] --host [host.symphony.com] --kmhost [keymanager.host.com]
```

If the script runs as expected it will obtain and log both session and key manager tokens, look up and log some details of the bot account and then create a datafeed and poll. If you send a message using the Symphony client to the bot account you should see the details logged.

#### Note
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
},
"homepage": "https://github.com/symphonyoss/hubot-symphony",
"dependencies": {
"fs": "0.0.2",
"backoff": "^2.5.0",
"html-entities": "^1.2.0",
"log": "^1.4.0",
"memoizee": "^0.4.1",
Expand All @@ -40,7 +40,7 @@
"cz-conventional-changelog": "^1.1.6",
"ghooks": "^1.3.2",
"hubot": ">=2.19",
"istanbul": "^0.4.4",
"istanbul": "^0.4.5",
"mocha": ">=2.5.3",
"nock": "^8.0.0",
"node-uuid": "^1.4.7",
Expand Down
18 changes: 13 additions & 5 deletions src/adapter.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ entities = new Entities()

class SymphonyAdapter extends Adapter

constructor: ->
super
constructor: (robot, @shutdownFunc) ->
super(robot)
throw new Error('HUBOT_SYMPHONY_HOST undefined') unless process.env.HUBOT_SYMPHONY_HOST
throw new Error('HUBOT_SYMPHONY_PUBLIC_KEY undefined') unless process.env.HUBOT_SYMPHONY_PUBLIC_KEY
throw new Error('HUBOT_SYMPHONY_PRIVATE_KEY undefined') unless process.env.HUBOT_SYMPHONY_PRIVATE_KEY
Expand All @@ -45,7 +45,12 @@ class SymphonyAdapter extends Adapter

run: =>
@robot.logger.info "Initialising..."
@symphony = new Symphony(process.env.HUBOT_SYMPHONY_HOST, process.env.HUBOT_SYMPHONY_PRIVATE_KEY, process.env.HUBOT_SYMPHONY_PUBLIC_KEY, process.env.HUBOT_SYMPHONY_PASSPHRASE)
host = process.env.HUBOT_SYMPHONY_HOST
privateKey = process.env.HUBOT_SYMPHONY_PRIVATE_KEY
publicKey = process.env.HUBOT_SYMPHONY_PUBLIC_KEY
passprhase = process.env.HUBOT_SYMPHONY_PASSPHRASE
keyManagerHost = process.env.HUBOT_SYMPHONY_KM_HOST ? host
@symphony = new Symphony(host, privateKey, publicKey, passprhase, keyManagerHost)
@symphony.whoAmI()
.then (response) =>
@robot.userId = response.userId
Expand Down Expand Up @@ -80,6 +85,7 @@ class SymphonyAdapter extends Adapter
@robot.emit 'error', new Error("Unable to create datafeed: #{response}")
.fail (err) =>
@robot.emit 'error', new Error("Unable to create datafeed: #{err}")
@shutdownFunc()

_pollDatafeed: (id) =>
# defer execution to ensure we don't go into an infinite polling loop
Expand Down Expand Up @@ -117,5 +123,7 @@ class SymphonyAdapter extends Adapter
@robot.brain.userForId(userId, existing)
existing

exports.use = (robot) ->
new SymphonyAdapter robot
exports.use = (robot, shutdownFunc) ->
new SymphonyAdapter robot, shutdownFunc ? () ->
@robot.logger.info 'Shutting down...'
process.exit 1
4 changes: 2 additions & 2 deletions src/diagnostic.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Log = require('log')
logger = new Log process.env.HUBOT_SYMPHONY_LOG_LEVEL or process.env.HUBOT_LOG_LEVEL or 'info'

argv = require('yargs')
.usage('Usage: $0 --publicKey [key1.pem] --privateKey [key2.pem] --passphrase [changeit] --host [host.symphony.com]')
.usage('Usage: $0 --publicKey [key1.pem] --privateKey [key2.pem] --passphrase [changeit] --host [host.symphony.com] --kmhost [keymanager.host.com]')
.demand(['publicKey', 'privateKey', 'host', 'passphrase'])
.argv

Expand All @@ -33,7 +33,7 @@ if argv.runOffline?

logger.info "Running diagnostics against https://#{argv.host}"

symphony = new Symphony(argv.host, argv.privateKey, argv.publicKey, argv.passphrase)
symphony = new Symphony(argv.host, argv.privateKey, argv.publicKey, argv.passphrase, argv.kmhost ? argv.host)

logger.info 'Connection initiated, starting tests...'

Expand Down
58 changes: 33 additions & 25 deletions src/symphony.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -24,73 +24,76 @@ memoize = require 'memoizee'

class Symphony

constructor: (@host, @privateKey, @publicKey, @passphrase) ->
constructor: (@host, @privateKey, @publicKey, @passphrase, @keyManagerHost) ->
@keyManagerHost = @keyManagerHost ? @host
logger.info "Connecting to #{@host}"
if @keyManagerHost isnt @host
logger.info "Using separate KeyManager #{@keyManagerHost}"
# refresh tokens on a weekly basis
weeklyRefresh = memoize @_httpPost, {maxAge: 604800000, length: 1}
@sessionAuth = => weeklyRefresh '/sessionauth/v1/authenticate'
@keyAuth = => weeklyRefresh '/keyauth/v1/authenticate'
weeklyRefresh = memoize @_httpPost, {maxAge: 604800000, length: 2}
@sessionAuth = => weeklyRefresh @host, '/sessionauth/v1/authenticate'
@keyAuth = => weeklyRefresh @keyManagerHost, '/keyauth/v1/authenticate'
Q.all([@sessionAuth(), @keyAuth()]).then (values) =>
logger.info "Initialising with sessionToken: #{values[0].token} and keyManagerToken: #{values[1].token}"

echo: (body) =>
@_httpAgentPost('/agent/v1/util/echo', body)
@_httpAgentPost('/agent/v1/util/echo', true, body)

whoAmI: =>
@_httpPodGet('/pod/v1/sessioninfo')
@_httpPodGet('/pod/v1/sessioninfo', true)

getUser: (userId) =>
@_httpPodGet('/pod/v1/admin/user/' + userId)
@_httpPodGet('/pod/v1/admin/user/' + userId, true)

sendMessage: (streamId, message, format) =>
body = {
message: message
format: format
}
@_httpAgentPost('/agent/v2/stream/' + streamId + '/message/create', body)
@_httpAgentPost('/agent/v2/stream/' + streamId + '/message/create', true, body)

getMessages: (streamId, since, limit = 100) =>
@_httpAgentGet('/agent/v2/stream/' + streamId + '/message')
@_httpAgentGet('/agent/v2/stream/' + streamId + '/message', true)

createDatafeed: =>
@_httpAgentPost('/agent/v1/datafeed/create')
@_httpAgentPost('/agent/v1/datafeed/create', true)

readDatafeed: (datafeedId) =>
@_httpAgentGet('/agent/v2/datafeed/' + datafeedId + '/read')
@_httpAgentGet('/agent/v2/datafeed/' + datafeedId + '/read', false)

_httpPodGet: (path, body) =>
_httpPodGet: (path, failUnlessHttp200) =>
@sessionAuth().then (value) =>
headers = {
sessionToken: value.token
}
@_httpGet(path, headers)
@_httpGet(@host, path, headers, failUnlessHttp200)

_httpAgentGet: (path, body) =>
_httpAgentGet: (path, failUnlessHttp200) =>
Q.all([@sessionAuth(), @keyAuth()]).then (values) =>
headers = {
sessionToken: values[0].token
keyManagerToken: values[1].token
}
@_httpGet(path, headers)
@_httpGet(@host, path, headers, failUnlessHttp200)

_httpAgentPost: (path, body) =>
_httpAgentPost: (path, failUnlessHttp200, body) =>
Q.all([@sessionAuth(), @keyAuth()]).then (values) =>
headers = {
sessionToken: values[0].token
keyManagerToken: values[1].token
}
@_httpPost(path, headers, body)
@_httpPost(@host, path, headers, failUnlessHttp200, body)

_httpGet: (path, headers = {}) =>
@_httpRequest('GET', path, headers)
_httpGet: (host, path, headers = {}, failUnlessHttp200) =>
@_httpRequest('GET', host, path, headers, failUnlessHttp200)

_httpPost: (path, headers = {}, body) =>
@_httpRequest('POST', path, headers, body)
_httpPost: (host, path, headers = {}, failUnlessHttp200, body) =>
@_httpRequest('POST', host, path, headers, failUnlessHttp200, body)

_httpRequest: (method, path, headers, body) =>
_httpRequest: (method, host, path, headers, failUnlessHttp200, body) =>
deferred = Q.defer()
options = {
baseUrl: 'https://' + @host
baseUrl: 'https://' + host
url: path
json: true
headers: headers
Expand All @@ -107,8 +110,13 @@ class Symphony
logger.warning "received #{res?.statusCode} error response from #{path}: #{err}"
deferred.reject(new Error(err))
else
logger.debug "received #{res?.statusCode} response from #{path}: #{JSON.stringify(data)}"
deferred.resolve data
if failUnlessHttp200 && res?.statusCode // 100 != 2
err = "received #{res?.statusCode} response from #{path}: #{JSON.stringify(data)}"
logger.warning err
deferred.reject new Error(err)
else
logger.debug "received #{res?.statusCode} response from #{path}: #{JSON.stringify(data)}"
deferred.resolve data
)
deferred.promise

Expand Down
18 changes: 18 additions & 0 deletions test/adapter-test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,21 @@ describe 'Adapter test suite', () ->
assert.isAtLeast((m for m in nock.messages when m.message is "<messageML><mention email=\"johndoe@symphony.com\"/> &lt;&amp;&gt;</messageML>").length, 1)
done()
adapter.run()

it 'should ignore http 400 errors when reading datafeed', (done) ->
nock.datafeedReadHttp400Count = 1
robot = new FakeRobot
adapter = SymphonyAdapter.use(robot)
adapter.on 'connected', () ->
assert.isDefined(adapter.symphony)
robot.on 'received', () ->
assert.isAtLeast((m for m in robot.received when m.text is 'Hello World').length, 1)
adapter.close()
done()
adapter.run()

it 'should exit datafeed cannot be created', (done) ->
nock.datafeedCreateHttp400Count = 1
robot = new FakeRobot
adapter = SymphonyAdapter.use robot, -> done()
adapter.run()
4 changes: 4 additions & 0 deletions test/fakes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ logger = new Log process.env.HUBOT_SYMPHONY_LOG_LEVEL or process.env.HUBOT_LOG_L
class FakeRobot extends EventEmitter

constructor: ->
# echo any errors
@on 'error', (err) ->
logger.error err

# noop the logging
@logs = {}
@logger = {
Expand Down
34 changes: 24 additions & 10 deletions test/nock-server.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ uuid = require 'node-uuid'

class NockServer extends EventEmitter

constructor: (@host, startWithHelloWorldMessage = true) ->
constructor: (@host, @kmHost, startWithHelloWorldMessage = true) ->
@kmHost = @kmHost ? @host
logger.info "Setting up mocks for #{@host}"

@streamId = 'WLwnGbzxIdU8ZmPUjAs_bn___qulefJUdA'
Expand All @@ -36,6 +37,10 @@ class NockServer extends EventEmitter

@datafeedId = 1234

@datafeedCreateHttp400Count = 0

@datafeedReadHttp400Count = 0

@messages = []
if startWithHelloWorldMessage
@messages.push({
Expand All @@ -56,16 +61,19 @@ class NockServer extends EventEmitter
name: 'sessionToken'
token: 'SESSION_TOKEN'
})
.post('/keyauth/v1/authenticate')
.reply(200, {
name: 'keyManagerToken'
token: 'KEY_MANAGER_TOKEN'
})
.post('/agent/v1/util/echo')
.reply(401, {
code: 401
message: 'Invalid session'
})
@keyAuthScope = nock(@kmHost)
.matchHeader('sessionToken', (val) -> !val?)
.matchHeader('keyManagerToken', (val) -> !val?)
.post('/keyauth/v1/authenticate')
.reply(200, {
name: 'keyManagerToken'
token: 'KEY_MANAGER_TOKEN'
})

@podScope = nock(@host)
.persist()
Expand Down Expand Up @@ -125,12 +133,18 @@ class NockServer extends EventEmitter
.get('/agent/v2/stream/' + @streamId + '/message')
.reply(200, (uri, requestBody) => JSON.stringify(@messages))
.post('/agent/v1/datafeed/create')
.reply(200, {
id: @datafeedId
})
.reply (uri, requestBody) =>
if @datafeedCreateHttp400Count-- > 0
[400, null]
else
[200, JSON.stringify {
id: @datafeedId
}]
.get('/agent/v2/datafeed/' + @datafeedId + '/read')
.reply (uri, requestBody) =>
if @messages.length == 0
if @datafeedReadHttp400Count-- > 0
[400, null]
else if @messages.length == 0
[204, null]
else
copy = @messages
Expand Down
20 changes: 20 additions & 0 deletions test/symphony-test.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@ Symphony = require '../src/symphony'
NockServer = require './nock-server'
{FakeRobot} = require './fakes'

describe 'On-premise key manager', () ->
nock = null
symphony = null

beforeEach ->
nock = new NockServer('https://foundation.symphony.com', 'https://keymanager.notsymphony.com')
symphony = new Symphony('foundation.symphony.com', './test/resources/privateKey.pem', './test/resources/publicKey.pem', 'changeit', 'keymanager.notsymphony.com')

afterEach ->
nock.close()

it 'should connect to separate key manager url', () ->
msg = { foo: 'bar' }
symphony.echo(msg)
.then (response) ->
assert.deepEqual(msg, response)
.fail (error) ->
assert.fail(0, 1,"Failed with error #{error}")


describe 'REST API test suite', () ->
nock = null
symphony = null
Expand Down