diff --git a/doc/DEPLOYMENT.md b/doc/DEPLOYMENT.md index 2d52dd6d..f2be6de9 100644 --- a/doc/DEPLOYMENT.md +++ b/doc/DEPLOYMENT.md @@ -325,7 +325,7 @@ ExecStart=/usr/bin/docker run --rm \ -e "SECRET=YOUR_SESSION_SECRET_HERE" \ -e "SAML_ID_PROVIDER_ENTRY_POINT_URL=YOUR_ID_PROVIDER_ENTRY_POINT" \ -e "SAML_ID_PROVIDER_ISSUER=YOUR_ID_PROVIDER_ISSUER" \ - -e "SAML_ID_PROVIDER_CERT_PATH=/etc/id_proider.cert" \ + -e "SAML_ID_PROVIDER_CERT_PATH=/etc/id_provider.cert" \ -p %i:3000 \ openstf/stf:latest \ stf auth-saml2 --port 3000 \ diff --git a/lib/units/api/controllers/user.js b/lib/units/api/controllers/user.js index b8ccc3f7..43953c2a 100644 --- a/lib/units/api/controllers/user.js +++ b/lib/units/api/controllers/user.js @@ -7,7 +7,7 @@ var util = require('util') var _ = require('lodash') var Promise = require('bluebird') var uuid = require('uuid') - +var adbkit = require('adbkit') var dbapi = require('../../../db/api') var logger = require('../../../util/logger') var datautil = require('../../../util/datautil') @@ -21,112 +21,22 @@ const jwtutil = require('../../../util/jwtutil') var log = logger.createLogger('api:controllers:user') -function getAccessToken(req, res) { - const id = req.swagger.params.id.value - - dbapi.loadAccessToken(id).then(function(token) { - if (!token || token.email !== req.user.email) { - apiutil.respond(res, 404, 'Not Found (access token)') - } - else { - apiutil.respond(res, 200, 'Access Token Information', { - token: apiutil.publishAccessToken(token) - }) - } - }) - .catch(function(err) { - apiutil.internalError(res, 'Failed to delete access token "%s": ', id, err.stack) - }) -} - -function getAccessTokens(req, res) { - dbapi.loadAccessTokens(req.user.email).then(function(cursor) { - Promise.promisify(cursor.toArray, cursor)().then(function(tokens) { - const tokenList = [] - - tokens.forEach(function(token) { - tokenList.push(apiutil.publishAccessToken(token)) - }) - apiutil.respond(res, 200, 'Access Tokens Information', {tokens: tokenList}) - }) - }) - .catch(function(err) { - apiutil.internalError(res, 'Failed to get access tokens: ', err.stack) - }) -} - -function createAccessToken(req, res) { - const title = req.swagger.params.title.value - const jwt = jwtutil.encode({ - payload: { - email: req.user.email - , name: req.user.name - } - , secret: req.options.secret - }) - const id = util.format('%s-%s', uuid.v4(), uuid.v4()).replace(/-/g, '') - - dbapi.saveUserAccessToken(req.user.email, { - title: title - , id: id - , jwt: jwt - }) - .then(function(stats) { - req.options.pushdev.send([ - req.user.group - , wireutil.envelope(new wire.UpdateAccessTokenMessage()) - ]) - apiutil.respond(res, 201, 'Created (access token)', - {token: apiutil.publishAccessToken(stats.changes[0].new_val)}) - }) - .catch(function(err) { - apiutil.internalError(res, 'Failed to create access token "%s": ', title, err.stack) - }) -} - -function deleteAccessTokens(req, res) { - dbapi.removeUserAccessTokens(req.user.email).then(function(stats) { - if (!stats.deleted) { - apiutil.respond(res, 200, 'Unchanged (access tokens)') - } - else { - req.options.pushdev.send([ - req.user.group - , wireutil.envelope(new wire.UpdateAccessTokenMessage()) - ]) - apiutil.respond(res, 200, 'Deleted (access tokens)') - } - }) - .catch(function(err) { - apiutil.internalError(res, 'Failed to delete access tokens: ', err.stack) - }) -} - -function deleteAccessToken(req, res) { - const id = req.swagger.params.id.value - - dbapi.loadAccessToken(id).then(function(token) { - if (!token || token.email !== req.user.email) { - apiutil.respond(res, 404, 'Not Found (access token)') - } - else { - dbapi.removeAccessToken(id).then(function(stats) { - if (!stats.deleted) { - apiutil.respond(res, 404, 'Not Found (access token)') - } - else { - req.options.pushdev.send([ - req.user.group - , wireutil.envelope(new wire.UpdateAccessTokenMessage()) - ]) - apiutil.respond(res, 200, 'Deleted (access token)') - } - }) - } - }) - .catch(function(err) { - apiutil.internalError(res, 'Failed to delete access token "%s": ', id, err.stack) - }) +module.exports = { + getUser: getUser +, getUserDevices: getUserDevices +, addUserDevice: addUserDevice +, getUserDeviceBySerial: getUserDeviceBySerial +, deleteUserDeviceBySerial: deleteUserDeviceBySerial +, remoteConnectUserDeviceBySerial: remoteConnectUserDeviceBySerial +, remoteDisconnectUserDeviceBySerial: remoteDisconnectUserDeviceBySerial +, getUserAccessTokens: getUserAccessTokens +, addAdbPublicKey: addAdbPublicKey +, addUserDeviceV2: addUserDevice +, getAccessTokens: getAccessTokens +, getAccessToken: getAccessToken +, createAccessToken: createAccessToken +, deleteAccessToken: deleteAccessToken +, deleteAccessTokens: deleteAccessTokens } function getUser(req, res) { @@ -525,19 +435,163 @@ function getUserAccessTokens(req, res) { }) } -module.exports = { - getUser: getUser -, getUserDevices: getUserDevices -, addUserDevice: addUserDevice -, addUserDeviceV2: addUserDevice -, getUserDeviceBySerial: getUserDeviceBySerial -, deleteUserDeviceBySerial: deleteUserDeviceBySerial -, remoteConnectUserDeviceBySerial: remoteConnectUserDeviceBySerial -, remoteDisconnectUserDeviceBySerial: remoteDisconnectUserDeviceBySerial -, getUserAccessTokens: getUserAccessTokens -, getAccessTokens: getAccessTokens -, getAccessToken: getAccessToken -, createAccessToken: createAccessToken -, deleteAccessToken: deleteAccessToken -, deleteAccessTokens: deleteAccessTokens +function addAdbPublicKey(req, res) { + var data = req.swagger.params.adb.value + adbkit.util.parsePublicKey(data.publickey) + .then(function(key) { + return dbapi.lookupUsersByAdbKey(key.fingerprint) + .then(function(cursor) { + return cursor.toArray() + }) + .then(function(users) { + return { + key: { + title: data.title || key.comment + , fingerprint: key.fingerprint + } + , users: users + } + }) + }) + .then(function(data) { + if (data.users.length) { + return res.json({ + success: true + }) + } + else { + return dbapi.insertUserAdbKey(req.user.email, data.key) + .then(function() { + return res.json({ + success: true + }) + }) + } + }) + .then(function() { + req.options.push.send([ + req.user.group + , wireutil.envelope(new wire.AdbKeysUpdatedMessage()) + ]) + }) + .catch(dbapi.DuplicateSecondaryIndexError, function() { + // No-op + return res.json({ + success: true + }) + }).catch(function(err) { + log.error('Failed to insert new adb key fingerprint: ', err.stack) + return res.status(500).json({ + success: false + , message: 'Unable to insert new adb key fingerprint to database' + }) + }) +} + +function getAccessToken(req, res) { + const id = req.swagger.params.id.value + + dbapi.loadAccessToken(id).then(function(token) { + if (!token || token.email !== req.user.email) { + apiutil.respond(res, 404, 'Not Found (access token)') + } + else { + apiutil.respond(res, 200, 'Access Token Information', { + token: apiutil.publishAccessToken(token) + }) + } + }) + .catch(function(err) { + apiutil.internalError(res, 'Failed to delete access token "%s": ', id, err.stack) + }) +} + +function getAccessTokens(req, res) { + dbapi.loadAccessTokens(req.user.email).then(function(cursor) { + Promise.promisify(cursor.toArray, cursor)().then(function(tokens) { + const tokenList = [] + + tokens.forEach(function(token) { + tokenList.push(apiutil.publishAccessToken(token)) + }) + apiutil.respond(res, 200, 'Access Tokens Information', {tokens: tokenList}) + }) + }) + .catch(function(err) { + apiutil.internalError(res, 'Failed to get access tokens: ', err.stack) + }) +} + +function createAccessToken(req, res) { + const title = req.swagger.params.title.value + const jwt = jwtutil.encode({ + payload: { + email: req.user.email + , name: req.user.name + } + , secret: req.options.secret + }) + const id = util.format('%s-%s', uuid.v4(), uuid.v4()).replace(/-/g, '') + + dbapi.saveUserAccessToken(req.user.email, { + title: title + , id: id + , jwt: jwt + }) + .then(function(stats) { + req.options.pushdev.send([ + req.user.group + , wireutil.envelope(new wire.UpdateAccessTokenMessage()) + ]) + apiutil.respond(res, 201, 'Created (access token)', + {token: apiutil.publishAccessToken(stats.changes[0].new_val)}) + }) + .catch(function(err) { + apiutil.internalError(res, 'Failed to create access token "%s": ', title, err.stack) + }) +} + +function deleteAccessTokens(req, res) { + dbapi.removeUserAccessTokens(req.user.email).then(function(stats) { + if (!stats.deleted) { + apiutil.respond(res, 200, 'Unchanged (access tokens)') + } + else { + req.options.pushdev.send([ + req.user.group + , wireutil.envelope(new wire.UpdateAccessTokenMessage()) + ]) + apiutil.respond(res, 200, 'Deleted (access tokens)') + } + }) + .catch(function(err) { + apiutil.internalError(res, 'Failed to delete access tokens: ', err.stack) + }) +} + +function deleteAccessToken(req, res) { + const id = req.swagger.params.id.value + + dbapi.loadAccessToken(id).then(function(token) { + if (!token || token.email !== req.user.email) { + apiutil.respond(res, 404, 'Not Found (access token)') + } + else { + dbapi.removeAccessToken(id).then(function(stats) { + if (!stats.deleted) { + apiutil.respond(res, 404, 'Not Found (access token)') + } + else { + req.options.pushdev.send([ + req.user.group + , wireutil.envelope(new wire.UpdateAccessTokenMessage()) + ]) + apiutil.respond(res, 200, 'Deleted (access token)') + } + }) + } + }) + .catch(function(err) { + apiutil.internalError(res, 'Failed to delete access token "%s": ', id, err.stack) + }) } diff --git a/lib/units/api/index.js b/lib/units/api/index.js index 26ca9de1..14929cdd 100644 --- a/lib/units/api/index.js +++ b/lib/units/api/index.js @@ -7,7 +7,8 @@ var path = require('path') var events = require('events') var express = require('express') -var SwaggerExpress = require('swagger-express-mw') +var swaggerExpress = require('swagger-express-mw') +var swaggerUi = require('swagger-tools/middleware/swagger-ui') var cookieSession = require('cookie-session') var Promise = require('bluebird') var _ = require('lodash') @@ -106,10 +107,11 @@ module.exports = function(options) { , swaggerFile: path.resolve(__dirname, 'swagger', 'api_v1.yaml') } - SwaggerExpress.create(config, function(err, swaggerExpress) { + swaggerExpress.create(config, function(err, swaggerExpress) { if (err) { throw err } + app.use(swaggerUi(swaggerExpress.runner.swagger)) swaggerExpress.register(app) }) diff --git a/lib/units/api/swagger/api_v1.yaml b/lib/units/api/swagger/api_v1.yaml index 05f2a6a6..e191fc8d 100644 --- a/lib/units/api/swagger/api_v1.yaml +++ b/lib/units/api/swagger/api_v1.yaml @@ -1670,6 +1670,41 @@ paths: $ref: "#/definitions/UnexpectedErrorResponse" security: - accessTokenAuth: [] + /user/adbPublicKeys: + x-swagger-router-controller: user + post: + summary: Adb public keys + description: Add adb public key for current user + operationId: addAdbPublicKey + consumes: + - application/json + produces: + - application/json + tags: + - user + parameters: + - name: adb + in: body + schema: + type: object + required: + - publickey + properties: + publickey: + type: string + description: adb public key (~/.android/id_rsa.pub) + title: + type: string + description: By default will be extracted from public key + responses: + "200": + description: Add adb key response + default: + description: Unexpected Error + schema: + $ref: "#/definitions/ErrorResponse" + security: + - accessTokenAuth: [] /devices: x-swagger-router-controller: devices get: diff --git a/package.json b/package.json index 7d63ecf6..e25c2831 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "stf-syrup": "^1.0.0", "stf-wiki": "^1.0.0", "swagger-express-mw": "^0.7.0", + "swagger-tools": "^0.10.3", "temp": "^0.8.1", "transliteration": "^1.1.6", "url-join": "1.1.0",