Skip to content

Commit

Permalink
feature: implement device access and new security spec (#620)
Browse files Browse the repository at this point in the history
* feature: provide a way for devices to obtain a security token and new user to sign up

* feature: add ability for ws clients that handle put to respond

* feature: add support for put via Web Sockets

* fix: remove href as it's handled by requestResponse code

* fix: prune old requst data

* fix: don't send context in request reponses

* fix: ensure result 202 on PENDING put reponses

* tests: add put tests

* test: complete WS put tests

* docs: update plugin documentation about Put requests

* refactor: rename plugin api registerActionHandler to registerPutHandler

registerActionHandler remains for backwards compatability

* fix: revert testing settings for pruning requests

* fix: make put test stop the server when done

* refactor: rename `result` to `statusCode`

* feature: add `authenticationRequired` to ws hello and `/signalk`

* fix: exception when throwing InvalidTokenError

* tests: reflect rename to `statusCode`

* docs: rename `result` to `statusCode`

* fix: exception when configuration is not allowed

* fix: exception if incoming request does not have a query

* refactor: remove authenticationRequired since it did not make it into the spec
  • Loading branch information
sbender9 authored and tkurki committed Nov 1, 2018
1 parent f80d4b6 commit c3a7440
Show file tree
Hide file tree
Showing 25 changed files with 2,455 additions and 580 deletions.
13 changes: 8 additions & 5 deletions SERVERPLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,20 @@ If the plugin needs to make and save changes to its options

If the plugin needs to read plugin options from disk

### app.registerActionHandler (context, path, source, callback)
### app.registerPutHandler (context, path, source, callback)

If the plugin wants to respond to actions, which are PUT requests for a specific path, it should register an action handler.
If the plugin wants to respond to PUT requests for a specific path, it should register an action handler.

The action handler can handle the request synchronously or asynchronously.
For synchronous actions the handler must return a value describing the result of the action: either `{ state: 'SUCCESS' }` or `{ state:'FAILURE', message:'Some Error Message' }`.

The passed callback should be a funtion taking the following arguments: (context, path, value, callback)

For synchronous actions the handler must return a value describing the response of the request: for example `{ state: 'COMPLETED', result:200 }` or `{ state:'COMPLETED', result:400, message:'Some Error Message' }`. The result value can be any valid http response code.

For asynchronous actions that may take considerable time and the requester should not be kept waiting for the result
the handler must return `{ state: 'PENDING' }`. When the action is finished the handler
should call the `callback` function with the result with `callback({ state: 'SUCCESS' })` or
`callback({ state:'FAILURE', message:'Some Error Message' })`.
should call the `callback` function with the result with `callback({ state: 'COMPLETED', statusCode:200 })` or
`callback({ state:'COMPLETED', statusCode:400, message:'Some Error Message' })`.

### app.registerDeltaInputHandler ((delta, next) => ...)

Expand Down
9 changes: 4 additions & 5 deletions lib/deltacache.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,10 @@ DeltaCache.prototype.getCachedDeltas = function (user, contextFilter, key) {

deltas = deltas.map(toDelta)

if (this.app.securityStrategy.shouldFilterDeltas()) {
deltas = deltas.filter(delta => {
return this.app.securityStrategy.filterReadDelta(user, delta)
})
}
deltas = deltas.filter(delta => {
return this.app.securityStrategy.filterReadDelta(user, delta)
})

return deltas
}

Expand Down
50 changes: 30 additions & 20 deletions lib/dummysecurity.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,36 @@
* limitations under the License.
*/

module.exports = function(app, config) {
module.exports = function (app, config) {
return {
getConfiguration: () => {
return {};
return {}
},

allowRestart: req => {
return false;
return false
},

allowConfigure: req => {
return false;
return false
},

getLoginStatus: req => {
return {
status: "notLoggedIn",
status: 'notLoggedIn',
readOnlyAccess: false,
authenticationRequired: false
};
}
},

getConfig: config => {
return config;
return config
},

setConfig: (config, newConfig) => {},

getUsers: config => {
return [];
return []
},

updateUser: (config, username, updates, callback) => {},
Expand All @@ -54,38 +54,48 @@ module.exports = function(app, config) {

deleteUser: (config, username, callback) => {},

shouldAllowWrite: function(req, delta) {
return true;
shouldAllowWrite: function (req, delta) {
return true
},

shouldAllowPut: function(req, context, source, path) {
return true;
shouldAllowPut: function (req, context, source, path) {
return true
},

filterReadDelta: (user, delta) => {
return delta;
return delta
},

verifyWS: spark => {},

authorizeWS: req => {},

checkACL: (id, context, path, source, operation) => {
return true;
return true
},

isDummy: () => {
return true;
return true
},

canAuthorizeWS: () => {
return false;
return false
},

shouldFilterDeltas: () => {
return false;
return false
},

addAdminMiddleware: () => {}
};
};
addAdminMiddleware: () => {},

allowReadOnly: () => {
return true
},

supportsLogin: () => false,

getAuthRequiredString: () => {
return 'never'
}
}
}
38 changes: 6 additions & 32 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ const express = require('express'),
getPrimaryPort = ports.getPrimaryPort,
getSecondaryPort = ports.getSecondaryPort,
getExternalPort = ports.getExternalPort,
{ startSecurity, getCertificateOptions } = require('./security.js'),
{
startSecurity,
getCertificateOptions,
getSecurityConfig,
saveSecurityConfig
} = require('./security.js'),
{ startDeltaStatistics, incDeltaStatistics } = require('./deltastats'),
DeltaChain = require('./deltachain')

Expand Down Expand Up @@ -415,34 +420,3 @@ Server.prototype.stop = function(cb) {
}
})
}

function pathForSecurityConfig(app) {
return path.join(app.config.configPath, 'security.json')
}

function saveSecurityConfig(app, data, callback) {
const config = JSON.parse(JSON.stringify(data))
const path = pathForSecurityConfig(app)
fs.writeFile(path, JSON.stringify(data, null, 2), err => {
if (!err) {
fs.chmodSync(path, '600')
}
if (callback) {
callback(err)
}
})
}

function getSecurityConfig(app) {
try {
const optionsAsString = fs.readFileSync(pathForSecurityConfig(app), 'utf8')
try {
return JSON.parse(optionsAsString)
} catch (e) {
console.error('Could not parse security config')
return {}
}
} catch (e) {
return {}
}
}
36 changes: 31 additions & 5 deletions lib/interfaces/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const modulesWithKeyword = require('../modules').modulesWithKeyword
const getLogger = require('../logging').getLogger
const _putPath = require('../put').putPath
const { getModulePublic } = require('../config/get')
const { queryRequest } = require('../requestResponse')

// #521 Returns path to load plugin-config assets.
const getPluginConfigPublic = getModulePublic('@signalk/plugin-config')
Expand Down Expand Up @@ -177,12 +178,35 @@ module.exports = function (app) {
return _.get(app.signalk.retrieve(), path)
}

function putSelfPath (path, value) {
return _putPath(app, `vessels.self.${path}`, { value: value })
function putSelfPath (path, value, updateCb) {
return _putPath(
app,
'vessels.self',
path,
{ value: value },
null,
null,
updateCb
)
}

function putPath (path, value) {
return _putPath(app, path, { value: value })
function putPath (path, value, updateCb) {
var parts = path.length > 0 ? path.split('.') : []

if (parts.length > 2) {
var context = `${parts[0]}.${parts[1]}`
var skpath = parts.slice(2).join('.')
}

return _putPath(
app,
context,
skpath,
{ value: value },
null,
null,
updateCb
)
}

function registerPlugin (app, pluginName, metadata, location) {
Expand Down Expand Up @@ -242,6 +266,7 @@ module.exports = function (app) {
getPath,
putSelfPath,
putPath,
queryRequest,
error: msg => {
console.error(`${packageName}:${msg}`)
},
Expand Down Expand Up @@ -287,11 +312,12 @@ module.exports = function (app) {
appCopy.readPluginOptions = () => {
return getPluginOptions(plugin.id)
}
appCopy.registerActionHandler = (context, path, callback) => {
appCopy.registerPutHandler = (context, path, callback) => {
onStopHandlers[plugin.id].push(
app.registerActionHandler(context, path, plugin.id, callback)
)
}
appCopy.registerActionHandler = appCopy.registerPutHandler

appCopy.registerHistoryProvider = provider => {
app.registerHistoryProvider(provider)
Expand Down
4 changes: 2 additions & 2 deletions lib/interfaces/rest.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,15 @@ module.exports = function (app) {
return
}
var last = app.deltaCache.buildFullFromDeltas(
req.skUser,
req.skPrincipal,
deltas
)
sendResult(last, path)
}
)
}
} else {
var last = app.deltaCache.buildFull(req.skUser, path)
var last = app.deltaCache.buildFull(req.skPrincipal, path)
sendResult(last, path)
}
})
Expand Down
Loading

0 comments on commit c3a7440

Please sign in to comment.