diff --git a/lib/units/api/controllers/device.js b/lib/units/api/controllers/devices.js similarity index 81% rename from lib/units/api/controllers/device.js rename to lib/units/api/controllers/devices.js index 703ccb67..4afc3b9f 100644 --- a/lib/units/api/controllers/device.js +++ b/lib/units/api/controllers/devices.js @@ -1,31 +1,37 @@ -var Promise = require('bluebird') var _ = require('lodash') +var Promise = require('bluebird') var dbapi = require('../../../db/api') var logger = require('../../../util/logger') var datautil = require('../../../util/datautil') -var log = logger.createLogger('api:controllers:device') +var log = logger.createLogger('api:controllers:devices') module.exports = { getDevices: getDevices , getDeviceBySerial: getDeviceBySerial } -var log = logger.createLogger('api:contoller:device') - function getDevices(req, res) { + var fields = req.swagger.params.fields.value + dbapi.loadDevices() .then(function(cursor) { return Promise.promisify(cursor.toArray, cursor)() .then(function(list) { + var deviceList = [] + list.forEach(function(device) { datautil.normalize(device, req.user) + if (fields) { + device = _.pick(device, fields.split(',')) + } + deviceList.push(device) }) res.json({ success: true - , devices: list + , devices: deviceList }) }) }) @@ -58,6 +64,7 @@ function getDeviceBySerial(req, res) { else { res.status(404).json({ success: false + , description: 'Device not found' }) } }) diff --git a/lib/units/api/controllers/token.js b/lib/units/api/controllers/token.js deleted file mode 100644 index f123b6d9..00000000 --- a/lib/units/api/controllers/token.js +++ /dev/null @@ -1,33 +0,0 @@ -var Promise = require('bluebird') - -var dbapi = require('../../../db/api') -var logger = require('../../../util/logger') - -var log = logger.createLogger('api:controllers:token') - -module.exports = { - getAccessTokens: getAccessTokens -} - -function getAccessTokens(req, res) { - dbapi.loadAccessTokens(req.user.email) - .then(function(cursor) { - return Promise.promisify(cursor.toArray, cursor)() - .then(function(list) { - var titles = [] - list.forEach(function(token) { - titles.push(token.title) - }) - res.json({ - success: true - , titles: titles - }) - }) - }) - .catch(function(err) { - log.error('Failed to load tokens: ', err.stack) - res.status(500).json({ - success: false - }) - }) -} diff --git a/lib/units/api/controllers/user.js b/lib/units/api/controllers/user.js index 7a592836..fcab8155 100644 --- a/lib/units/api/controllers/user.js +++ b/lib/units/api/controllers/user.js @@ -1,4 +1,7 @@ +var util = require('util') + var Promise = require('bluebird') +var _ = require('lodash') var dbapi = require('../../../db/api') var logger = require('../../../util/logger') @@ -9,45 +12,96 @@ var wireutil = require('../../../wire/util') var log = logger.createLogger('api:controllers:user') module.exports = { - getCurrentUser: getCurrentUser -, getCurrentUserDevices: getCurrentUserDevices -, addDeviceToUser: addDeviceToUser -, deleteDeviceFromUser: deleteDeviceFromUser + getUser: getUser +, getUserDevices: getUserDevices +, addUserDevice: addUserDevice , getUserDeviceBySerial: getUserDeviceBySerial -, connectDeviceBySerial: connectDeviceBySerial -, disconnectDeviceBySerial: disconnectDeviceBySerial +, deleteUserDeviceBySerial: deleteUserDeviceBySerial +, remoteConnectUserDeviceBySerial: remoteConnectUserDeviceBySerial +, remoteDisconnectUserDeviceBySerial: remoteDisconnectUserDeviceBySerial +, getUserAccessTokens: getUserAccessTokens } -function getCurrentUser(req, res) { +function getUser(req, res) { res.json({ success: true , user: req.user }) } -function getCurrentUserDevices(req, res) { +function getUserDevices(req, res) { + var fields = req.swagger.params.fields.value + dbapi.loadUserDevices(req.user.email) .then(function(cursor) { return Promise.promisify(cursor.toArray, cursor)() .then(function(list) { + var deviceList = [] + list.forEach(function(device) { datautil.normalize(device, req.user) + if (fields) { + device = _.pick(device, fields.split(',')) + } + deviceList.push(device) }) + res.json({ success: true - , devices: list + , devices: deviceList }) }) }) .catch(function(err) { - log.error('Failed to load group: ', err.stack) + log.error('Failed to load device list: ', err.stack) res.status(500).json({ success: false }) }) } -function addDeviceToUser(req, res) { +function getUserDeviceBySerial(req, res) { + var serial = req.swagger.params.serial.value + var fields = req.swagger.params.fields.value + + dbapi.loadDevice(serial) + .then(function(device) { + if (device) { + datautil.normalize(device, req.user) + + if (device.owner && device.owner.email === req.user.email) { + if(fields) { + device = _.pick(device, fields.split(',')) + } + + res.json({ + success: true + , device: device + }) + } + else { + res.status(401).json({ + success: false + , description: 'Device is not owned by you' + }) + } + } + else { + res.status(404).json({ + success: false + , description: 'Device not found' + }) + } + }) + .catch(function(err) { + log.error('Failed to load device "%s": ', req.params.serial, err.stack) + res.status(500).json({ + success: false + }) + }) +} + +function addUserDevice(req, res) { var serial = req.body.serial var timeout = req.body.timeout || null @@ -81,17 +135,23 @@ function addDeviceToUser(req, res) { res.status(202).json({ success: true - , description: 'Device Add request is accepted' + , description: 'Device Add request is accepted. Check if device is successfully added using pollingUrl' + , pollingUrl: util.format('%s://%s%s/user/devices/%s' + , req.protocol + , req.get('host') + , req.swagger.operation.api.basePath + , serial + ) }) } else { - res.status(500).json({ + res.status(401).json({ success: false , description: 'Device is being used or not available' }) } } else { - res.status(500).json({ + res.status(404).json({ success: false , description: 'Bad device serial' }) @@ -99,7 +159,7 @@ function addDeviceToUser(req, res) { }) } -function deleteDeviceFromUser(req, res) { +function deleteUserDeviceBySerial(req, res) { var serial = req.swagger.params.serial.value dbapi.loadDevice(serial) @@ -126,17 +186,23 @@ function deleteDeviceFromUser(req, res) { res.status(202).json({ success: true - , description: 'Device Release request is accepted' + , description: 'Device Release request is accepted. Check if device is successfully removed using pollingUrl' + , pollingUrl: util.format('%s://%s%s/user/devices/%s' + , req.protocol + , req.get('host') + , req.swagger.operation.api.basePath + , serial + ) }) } else { - res.status(500).json({ + res.status(401).json({ success: false , description: 'You cannot kick this device' }) } } else { - res.status(500).json({ + res.status(404).json({ success: false , description: 'Bad device serial' }) @@ -144,47 +210,7 @@ function deleteDeviceFromUser(req, res) { }) } -function getUserDeviceBySerial(req, res) { - var serial = req.swagger.params.serial.value - var fields = req.swagger.params.fields.value - - dbapi.loadDevice(serial) - .then(function(device) { - if (device) { - datautil.normalize(device, req.user) - - if (device.owner && device.owner === req.user.email) { - if(fields) { - device = _.pick(device, fields.split(',')) - } - - res.json({ - success: true - , device: device - }) - } - else { - res.status(404).json({ - success: false - , description: 'device is not owned by you' - }) - } - } - else { - res.status(404).json({ - success: false - }) - } - }) - .catch(function(err) { - log.error('Failed to load device "%s": ', req.params.serial, err.stack) - res.status(500).json({ - success: false - }) - }) -} - -function connectDeviceBySerial(req, res) { +function remoteConnectUserDeviceBySerial(req, res) { var serial = req.swagger.params.serial.value dbapi.loadDevice(serial) @@ -192,7 +218,7 @@ function connectDeviceBySerial(req, res) { if (device) { datautil.normalize(device, req.user) - if (device.present && device.ready && device.using && device.owner.email == req.user.email) { + if (device.present && device.ready && device.using && device.owner.email === req.user.email) { req.options.push.send([ device.channel , wireutil.envelope( @@ -202,11 +228,17 @@ function connectDeviceBySerial(req, res) { res.status(202).json({ success: true - , description: 'Device Connect request is accepted' + , description: 'Device Connect request is accepted. Check if device is successfully connected using pollingUrl' + , pollingUrl: util.format('%s://%s%s/user/devices/%s' + , req.protocol + , req.get('host') + , req.swagger.operation.api.basePath + , serial + ) }) } else { - res.status(500).json({ + res.status(401).json({ success: false , description: 'Device is not owned by you or is not available' }) @@ -215,7 +247,7 @@ function connectDeviceBySerial(req, res) { else { res.status(404).json({ success: false - , description: 'Bad device serial' + , description: 'Device not found' }) } }) @@ -227,7 +259,7 @@ function connectDeviceBySerial(req, res) { }) } -function disconnectDeviceBySerial(req, res) { +function remoteDisconnectUserDeviceBySerial(req, res) { var serial = req.swagger.params.serial.value dbapi.loadDevice(serial) @@ -245,11 +277,17 @@ function disconnectDeviceBySerial(req, res) { res.status(202).json({ success: true - , description: 'Device Disonnect request is accepted' + , description: 'Device Disonnect request is accepted. Check if device is successfully disconnected using pollingUrl' + , pollingUrl: util.format('%s://%s%s/user/devices/%s' + , req.protocol + , req.get('host') + , req.swagger.operation.api.basePath + , serial + ) }) } else { - res.status(500).json({ + res.status(401).json({ success: false , description: 'Device is not owned by you or is not available' }) @@ -258,7 +296,7 @@ function disconnectDeviceBySerial(req, res) { else { res.status(404).json({ success: false - , description: 'Bad device serial' + , description: 'Device not found' }) } }) @@ -269,3 +307,26 @@ function disconnectDeviceBySerial(req, res) { }) }) } + +function getUserAccessTokens(req, res) { + dbapi.loadAccessTokens(req.user.email) + .then(function(cursor) { + return Promise.promisify(cursor.toArray, cursor)() + .then(function(list) { + var titles = [] + list.forEach(function(token) { + titles.push(token.title) + }) + res.json({ + success: true + , titles: titles + }) + }) + }) + .catch(function(err) { + log.error('Failed to load tokens: ', err.stack) + res.status(500).json({ + success: false + }) + }) +} diff --git a/lib/units/api/helpers/securityHandlers.js b/lib/units/api/helpers/securityHandlers.js index 7843ab8c..9c5fa3ee 100644 --- a/lib/units/api/helpers/securityHandlers.js +++ b/lib/units/api/helpers/securityHandlers.js @@ -1,7 +1,7 @@ +var dbapi = require('../../../db/api') var jwtutil = require('../../../util/jwtutil') var urlutil = require('../../../util/urlutil') var logger = require('../../../util/logger') -var dbapi = require('../../../db/api') var log = logger.createLogger('api:helpers:securityHandlers') @@ -11,7 +11,16 @@ module.exports = { function accessTokenAuth(req, res, next) { if (req.headers.authorization) { - var tokenId = req.headers.authorization.split(" ")[1] + var authHeader = req.headers.authorization.split(' ') + , format = authHeader[0] + , tokenId = authHeader[1] + + if (format !== 'bearer') { + res.status(401).json({ + success: false + , description: 'Authorization header should be in "bearer $AUTH_TOKEN" format' + }) + } if (tokenId) { dbapi.loadAccessToken(tokenId) @@ -28,23 +37,23 @@ function accessTokenAuth(req, res, next) { } }) } else { - res.json(500, { + res.status(500).json({ success: false }) } }) .catch(function(err) { log.error('Failed to load token: ', err.stack) - res.json(401, { + res.status(401).json({ success: false, - description: 'Bad credentials' + description: 'Bad Credentials' }) }) } else { log.error('Bad Access Token Header') - res.json(401, { + res.status(401).json({ success: false, - description: 'Bad credentials' + description: 'Bad Credentials' }) } } @@ -67,9 +76,9 @@ function accessTokenAuth(req, res, next) { .catch(next) } else { - res.json(401, { + res.status(401).json({ success: false, - description: 'Requires authentication' + description: 'Requires Authentication' }) } } diff --git a/lib/units/api/swagger/api_v1.yaml b/lib/units/api/swagger/api_v1.yaml index ead63bd8..aa455bdd 100644 --- a/lib/units/api/swagger/api_v1.yaml +++ b/lib/units/api/swagger/api_v1.yaml @@ -2,12 +2,12 @@ swagger: "2.0" info: version: "2.0.0" title: Smartphone Test Farm - description: Control and manager real Smartphone devices from browser and restful apis + description: Control and manages real Smartphone devices from browser and restful apis license: name: Apache-2.0 url: http://www.apache.org/licenses/LICENSE-2.0 contact: - name: STF Support + name: STF Team email: contact@openstf.io url: http://openstf.io/ basePath: /api/v1 @@ -21,15 +21,15 @@ produces: tags: - name: user description: User Operations - - name: device + - name: devices description: Device Operations paths: /user: x-swagger-router-controller: user get: summary: User Profile - description: The User Profile endpoint returns information about current authorized user. - operationId: getCurrentUser + description: The User Profile endpoint returns information about current authorized user + operationId: getUser tags: - user responses: @@ -47,13 +47,19 @@ paths: x-swagger-router-controller: user get: summary: List devices owned by current user - description: The User Devices endpoint returns information about user group of current authorized user. - operationId: getCurrentUserDevices + description: The User Devices endpoint returns device list owner by current authorized user + operationId: getUserDevices tags: - user + parameters: + - name: fields + in: query + description: Fields query parameter takes a comma seperated list of fields. Only listed field will be return in response + required: false + type: string responses: "200": - description: Current User Devices information + description: Current User Devices List schema: $ref: "#/definitions/DeviceListResponse" default: @@ -63,21 +69,23 @@ paths: security: - accessTokenAuth: [] post: - summary: Add device to a user - description: The User Devices endpoint will request stf server for a new device. It will return request accepted if device is usable. - operationId: addDeviceToUser + summary: Add a device to a user + description: The User Devices endpoint will request stf server for a new device. It will return request accepted if device is usable + operationId: addUserDevice tags: - user parameters: - - name: device + - name: devices in: body description: Device to add required: true schema: - $ref: "#/definitions/DeviceAddPayload" + $ref: "#/definitions/AddUserDevicePayload" responses: "202": - description: Device Add Request Status + description: Add User Device Request Status and polling Url + schema: + $ref: "#/definitions/AddUserDeviceResponse" default: description: Unexpected Error schema: @@ -88,7 +96,7 @@ paths: x-swagger-router-controller: user get: summary: Device Information - description: The device enpoint return information about a single device. + description: The device enpoint return information about device owned by user operationId: getUserDeviceBySerial tags: - user @@ -105,9 +113,9 @@ paths: type: string responses: "200": - description: Device Information + description: Delete User Device Request Status and polling Url schema: - $ref: "#/definitions/DeviceResponse" + $ref: "#/definitions/DeleteUserDeviceBySerialResponse" default: description: Unexpected Error schema: @@ -116,8 +124,8 @@ paths: - accessTokenAuth: [] delete: summary: Release device from user - description: The User Devices endpoint will request for device release from stf server. - operationId: deleteDeviceFromUser + description: The User Devices endpoint will request for device release from stf server. It will return request accepted if device is being used by current user + operationId: deleteUserDeviceBySerial tags: - user parameters: @@ -142,7 +150,7 @@ paths: post: summary: Remote Connect description: The device connect endpoint will request stf server to connect remotely - operationId: connectDeviceBySerial + operationId: remoteConnectUserDeviceBySerial tags: - user parameters: @@ -153,7 +161,9 @@ paths: type: string responses: "202": - description: Device Connect Request Status + description: Remote Connect User Device Request Status + schema: + $ref: "#/definitions/RemoteConnectUserDeviceResponse" default: description: Unexpected Error schema: @@ -163,7 +173,7 @@ paths: delete: summary: Remote Disconnect description: The device connect endpoint will request stf server to disconnect remotely - operationId: disconnectDeviceBySerial + operationId: remoteDisconnectUserDeviceBySerial tags: - user parameters: @@ -174,7 +184,9 @@ paths: type: string responses: "202": - description: Device Disconnect Request Status + description: Remote Disonnect User Device Request Status + schema: + $ref: "#/definitions/RemoteDisconnectUserDeviceResponse" default: description: Unexpected Error schema: @@ -185,8 +197,8 @@ paths: x-swagger-router-controller: token get: summary: Access Tokens - description: The Access Tokens endpoints returns titles of all the valid access tokens. - operationId: getAccessTokens + description: The Access Tokens endpoints returns titles of all the valid access tokens + operationId: getUserAccessTokens tags: - user responses: @@ -201,13 +213,19 @@ paths: security: - accessTokenAuth: [] /devices: - x-swagger-router-controller: device + x-swagger-router-controller: devices get: summary: Device List - description: The devices enpoint return list of all the STF devices including Disconnected and Offline + description: The devices endpoint return list of all the STF devices including Disconnected and Offline operationId: getDevices tags: - - device + - devices + parameters: + - name: fields + in: query + description: Fields query parameter takes a comma seperated list of fields. Only listed field will be return in response + required: false + type: string responses: "200": description: List of Devices @@ -220,13 +238,13 @@ paths: security: - accessTokenAuth: [] /devices/{serial}: - x-swagger-router-controller: device + x-swagger-router-controller: devices get: summary: Device Information - description: The device enpoint return information about a single device. + description: The device enpoint return information about a single device operationId: getDeviceBySerial tags: - - device + - devices parameters: - name: serial in: path @@ -280,13 +298,37 @@ definitions: properties: device: type: object + AddUserDeviceResponse: + required: + - pollingUrl + properties: + pollingUrl: + type: string + DeleteUserDeviceBySerialResponse: + required: + - pollingUrl + properties: + pollingUrl: + type: string + RemoteDisconnectUserDeviceResponse: + required: + - pollingUrl + properties: + pollingUrl: + type: string + RemoteConnectUserDeviceResponse: + required: + - pollingUrl + properties: + pollingUrl: + type: string ErrorResponse: required: - message properties: message: type: string - DeviceAddPayload: + AddUserDevicePayload: description: payload object for adding device to user required: - serial @@ -295,11 +337,11 @@ definitions: description: Device Serial type: string timeout: - description: Device timeout in ms. If device is kept idle for this period, it will be automatically disconnected. Default is provider group timeout. + description: Device timeout in ms. If device is kept idle for this period, it will be automatically disconnected. Default is provider group timeout type: integer securityDefinitions: accessTokenAuth: type: apiKey - name: accessTokenAuth + name: authorization in: header