Skip to content

Commit

Permalink
Add capability checks everywhere, refs noflo/noflo-ui#278
Browse files Browse the repository at this point in the history
  • Loading branch information
bergie committed Mar 26, 2015
1 parent 428191f commit 8810422
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 9 deletions.
23 changes: 19 additions & 4 deletions spec/Graph.coffee
Expand Up @@ -13,6 +13,8 @@ describe 'Graph protocol', ->

beforeEach ->
runtime = new direct.Runtime
permissions:
foo: ['protocol:graph']
client = new direct.Client runtime
client.connect()
client2 = new direct.Client runtime
Expand All @@ -25,10 +27,20 @@ describe 'Graph protocol', ->
runtime = null

describe 'sending graph:clear', ->
it 'should fail without proper authentication', (done) ->
payload =
id: 'mygraph'
main: true
client.once 'message', (msg) ->
chai.expect(msg.protocol).to.equal 'graph'
chai.expect(msg.command).to.equal 'error'
done()
client.send 'graph', 'clear', payload
it 'should respond with graph:clear', (done) ->
payload =
id: 'mygraph'
main: true
secret: 'foo'
client.once 'message', (msg) ->
chai.expect(msg.protocol).to.equal 'graph'
chai.expect(msg.command).to.equal 'clear'
Expand All @@ -40,6 +52,7 @@ describe 'Graph protocol', ->
payload =
id: 'mygraph'
main: true
secret: 'foo'
client2.once 'message', (msg) ->
chai.expect(msg.protocol).to.equal 'graph'
chai.expect(msg.command).to.equal 'clear'
Expand All @@ -55,6 +68,8 @@ describe 'Graph protocol', ->
component: 'Component1'
graph: graph
metadata: {}
authenticatedPayload = JSON.parse JSON.stringify payload
authenticatedPayload.secret = 'foo'
checkAddNode = (msg, done) ->
return if msg.command != 'addnode'
chai.expect(msg.protocol).to.equal 'graph'
Expand All @@ -63,9 +78,9 @@ describe 'Graph protocol', ->
done()
it 'should respond with graph:addnode', (done) ->
client.on 'message', (msg) -> checkAddNode msg, done
client.send 'graph', 'clear', { id: graph, main: true }
client.send 'graph', 'addnode', payload
client.send 'graph', 'clear', { id: graph, main: true, secret: 'foo'}
client.send 'graph', 'addnode', authenticatedPayload
it 'should send to all clients', (done) ->
client2.on 'message', (msg) -> checkAddNode msg, done
client.send 'graph', 'clear', { id: graph, main: true }
client.send 'graph', 'addnode', payload
client.send 'graph', 'clear', { id: graph, main: true, secret: 'foo' }
client.send 'graph', 'addnode', authenticatedPayload
21 changes: 19 additions & 2 deletions spec/Runtime.coffee
Expand Up @@ -20,11 +20,28 @@ describe 'Runtime protocol', ->
runtime = null

describe 'sending runtime:getruntime', ->
it 'should respond with runtime:runtime', (done) ->
it 'should respond with runtime:runtime for unauthorized user', (done) ->
client.once 'message', (msg) ->
chai.expect(msg.protocol).to.equal 'runtime'
chai.expect(msg.command).to.equal 'runtime'
chai.expect(msg.payload.type).to.have.string 'noflo'
chai.expect(msg.payload.capabilities).to.include 'protocol:graph'
chai.expect(msg.payload.capabilities).to.eql []
chai.expect(msg.payload.allCapabilities).to.include 'protocol:graph'
done()
client.send 'runtime', 'getruntime', null
it 'should respond with runtime:runtime for authorized user', (done) ->
client.disconnect()
runtime = new direct.Runtime
permissions:
'super-secret': ['protocol:graph', 'protocol:component', 'unknown:capability']
client = new direct.Client runtime
client.connect()
client.once 'message', (msg) ->
chai.expect(msg.protocol).to.equal 'runtime'
chai.expect(msg.command).to.equal 'runtime'
chai.expect(msg.payload.type).to.have.string 'noflo'
chai.expect(msg.payload.capabilities).to.eql ['protocol:graph', 'protocol:component']
chai.expect(msg.payload.allCapabilities).to.include 'protocol:graph'
done()
client.send 'runtime', 'getruntime',
secret: 'super-secret'
33 changes: 32 additions & 1 deletion src/Base.coffee
Expand Up @@ -9,7 +9,7 @@ protocols =
class BaseTransport
constructor: (@options) ->
@options = {} unless @options
@version = '0.4'
@version = '0.5'
@component = new protocols.Component @
@graph = new protocols.Graph @
@network = new protocols.Network @
Expand All @@ -27,6 +27,33 @@ class BaseTransport
# Start capturing so that we can send it to the UI when it connects
@startCapture()

unless @options.defaultPermissions
# Default: no capabilities granted for anonymous users
@options.defaultPermissions = []

unless @options.permissions
@options.permissions = {}

# Check if a given user is authorized for a given capability
#
# @param [String] Capability to check
# @param [String] Secret provided by user
canDo: (capability, secret) ->
permitted = @getPermitted secret
if permitted.indexOf(capability) isnt -1
return true
false

# Get enabled capabilities for a user
#
# @param [String] Secret provided by user
getPermitted: (secret) ->
unless secret
return @options.defaultPermissions
unless @options.permissions[secret]
return []
@options.permissions[secret]

# Send a message back to the user via the transport protocol.
#
# Each transport implementation should provide their own implementation
Expand All @@ -44,6 +71,9 @@ class BaseTransport

# Send a message to *all users* via the transport protocol
#
# The transport should verify that the recipients are authorized to receive
# the message by using the `canDo` method.
#
# Like send() only it sends to all.
# @param [Object] Message context, can be null
sendAll: (protocol, topic, payload, context) ->
Expand All @@ -61,6 +91,7 @@ class BaseTransport
# @param [Object] Message payload
# @param [Object] Message context, dependent on the transport
receive: (protocol, topic, payload, context) ->
payload = {} unless payload
@context = context
switch protocol
when 'runtime' then @runtime.receive topic, payload, context
Expand Down
12 changes: 12 additions & 0 deletions src/protocol/Component.coffee
Expand Up @@ -9,6 +9,18 @@ class ComponentProtocol
@transport.send 'component', topic, payload, context

receive: (topic, payload, context) ->
unless @transport.canDo 'protocol:component', payload.secret
@send 'error', "#{topic} not permitted", context
return

if topic is 'source' and not @transport.canDo 'component:setsource'
@send 'error', "#{topic} not permitted", context
return

if topic is 'getsource' and not @transport.canDo 'component:getsource'
@send 'error', "#{topic} not permitted", context
return

switch topic
when 'list' then @listComponents payload, context
when 'getsource' then @getSource payload, context
Expand Down
3 changes: 3 additions & 0 deletions src/protocol/Graph.coffee
Expand Up @@ -11,6 +11,9 @@ class GraphProtocol
@transport.sendAll 'graph', topic, payload

receive: (topic, payload, context) ->
unless @transport.canDo 'protocol:graph', payload.secret
@send 'error', "#{topic} not permitted", context
return

# Find locally stored graph by ID
if topic isnt 'clear'
Expand Down
4 changes: 4 additions & 0 deletions src/protocol/Network.coffee
Expand Up @@ -60,6 +60,10 @@ class NetworkProtocol extends EventEmitter
@transport.sendAll 'network', topic, payload

receive: (topic, payload, context) ->
unless @transport.canDo 'protocol:network', payload.secret
@send 'error', "#{topic} not permitted", context
return

if topic isnt 'list'
graph = @resolveGraph payload, context
return unless graph
Expand Down
13 changes: 11 additions & 2 deletions src/protocol/Runtime.coffee
Expand Up @@ -5,7 +5,6 @@ class RuntimeProtocol
@mainGraph = null
@outputSockets = {} # publicPort -> noflo.Socket


@transport.network.on 'addnetwork', (network) =>
network.on 'start', () =>
# processes don't exist until started
Expand All @@ -24,6 +23,10 @@ class RuntimeProtocol
@transport.sendAll 'runtime', topic, payload

receive: (topic, payload, context) ->
if topic is 'packet' and not @transport.canDo 'protocol:runtime', payload.secret
@send 'error', "#{topic} not permitted", context
return

switch topic
when 'getruntime' then @getRuntime payload, context
when 'packet' then @receivePacket payload, context
Expand All @@ -35,6 +38,7 @@ class RuntimeProtocol
type = 'noflo-browser'
else
type = 'noflo-nodejs'

capabilities = @transport.options.capabilities
unless capabilities
capabilities = [
Expand All @@ -45,14 +49,19 @@ class RuntimeProtocol
'component:setsource'
'component:getsource'
]

permittedCapabilities = capabilities.filter (capability) =>
@transport.canDo capability, payload.secret

graph = undefined
for k, v of @transport.network.networks
graph = k
break
@send 'runtime',
type: type
version: @transport.version
capabilities: capabilities
capabilities: permittedCapabilities
allCapabilities: capabilities
graph: graph
, context
graphInstance = @transport.graph.graphs[graph]
Expand Down

0 comments on commit 8810422

Please sign in to comment.