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

Commit

Permalink
Merge pull request #13 from symphonyoss/dev
Browse files Browse the repository at this point in the history
Dev changes
  • Loading branch information
jonfreedman committed Sep 2, 2016
2 parents 0a9ec3e + ef4a36d commit f47dfe0
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 44 deletions.
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

0 comments on commit f47dfe0

Please sign in to comment.