Add SSH key management API

This commit is contained in:
Hideki Nakamura 2017-12-07 23:11:24 +09:00
parent 304c597a2f
commit 923893e160
8 changed files with 1140 additions and 46 deletions

View File

@ -1,92 +1,100 @@
{
"name" : "node-red",
"version" : "0.17.5",
"description" : "A visual tool for wiring the Internet of Things",
"homepage" : "http://nodered.org",
"license" : "Apache-2.0",
"repository" : {
"type":"git",
"url":"https://github.com/node-red/node-red.git"
"name": "node-red",
"version": "0.17.5",
"description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/node-red/node-red.git"
},
"main" : "red/red.js",
"scripts" : {
"main": "red/red.js",
"scripts": {
"start": "node red.js",
"test": "grunt",
"build": "grunt build"
},
"bin" : {
"bin": {
"node-red": "./red.js",
"node-red-pi": "bin/node-red-pi"
},
"contributors": [
{"name": "Nick O'Leary"},
{"name": "Dave Conway-Jones"}
{
"name": "Nick O'Leary"
},
{
"name": "Dave Conway-Jones"
}
],
"keywords": [
"editor", "messaging", "iot", "flow"
"editor",
"messaging",
"iot",
"flow"
],
"dependencies": {
"basic-auth": "1.1.0",
"bcryptjs": "2.4.3",
"body-parser": "1.17.2",
"cheerio":"0.22.0",
"cheerio": "0.22.0",
"clone": "2.1.1",
"cookie": "0.3.1",
"cookie-parser": "1.4.3",
"cors":"2.8.3",
"cron":"1.2.1",
"cors": "2.8.3",
"cron": "1.2.1",
"express": "4.15.3",
"express-session": "1.15.2",
"follow-redirects":"1.2.4",
"follow-redirects": "1.2.4",
"fs-extra": "4.0.2",
"fs.notify":"0.0.4",
"hash-sum":"1.0.2",
"i18next":"1.10.6",
"is-utf8":"0.2.1",
"fs.notify": "0.0.4",
"hash-sum": "1.0.2",
"i18next": "1.10.6",
"is-utf8": "0.2.1",
"js-yaml": "3.8.4",
"json-stringify-safe":"5.0.1",
"jsonata":"1.2.6",
"json-stringify-safe": "5.0.1",
"jsonata": "1.2.6",
"media-typer": "0.3.0",
"mqtt": "2.9.0",
"multer": "1.3.0",
"mustache": "2.3.0",
"node-red-node-email": "0.1.*",
"node-red-node-feedparser": "0.1.*",
"node-red-node-rbe": "0.1.*",
"node-red-node-twitter": "0.1.*",
"nopt": "3.0.6",
"oauth2orize":"1.8.0",
"on-headers":"1.0.1",
"passport":"0.3.2",
"passport-http-bearer":"1.0.1",
"passport-oauth2-client-password":"0.1.2",
"raw-body":"2.2.0",
"oauth2orize": "1.8.0",
"on-headers": "1.0.1",
"passport": "0.3.2",
"passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2",
"raw-body": "2.2.0",
"semver": "5.3.0",
"sentiment":"2.1.0",
"uglify-js":"3.0.20",
"sentiment": "2.1.0",
"ssh-keygen": "^0.4.1",
"uglify-js": "3.0.20",
"when": "3.7.8",
"ws": "1.1.1",
"xml2js":"0.4.17",
"node-red-node-feedparser":"0.1.*",
"node-red-node-email":"0.1.*",
"node-red-node-twitter":"0.1.*",
"node-red-node-rbe":"0.1.*"
"xml2js": "0.4.17"
},
"optionalDependencies": {
"bcrypt":"~1.0.1"
"bcrypt": "~1.0.1"
},
"devDependencies": {
"grunt": "~1.0.1",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.2.0",
"grunt-concurrent":"~2.3.1",
"grunt-contrib-clean":"~1.1.0",
"grunt-concurrent": "~2.3.1",
"grunt-contrib-clean": "~1.1.0",
"grunt-contrib-compress": "~1.4.0",
"grunt-contrib-concat":"~1.0.1",
"grunt-contrib-concat": "~1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-jshint": "~1.1.0",
"grunt-contrib-uglify": "~3.0.1",
"grunt-contrib-watch":"~1.0.0",
"grunt-jsonlint":"~1.1.0",
"grunt-contrib-watch": "~1.0.0",
"grunt-jsonlint": "~1.1.0",
"grunt-mocha-istanbul": "5.0.2",
"grunt-nodemon":"~0.4.2",
"grunt-sass":"~1.2.1",
"grunt-nodemon": "~0.4.2",
"grunt-sass": "~1.2.1",
"grunt-simple-mocha": "~0.4.1",
"istanbul": "0.4.5",
"mocha": "~3.4.2",

View File

@ -97,6 +97,10 @@ module.exports = {
// User Settings
editorApp.post("/settings/user",needsPermission("settings.write"),info.updateUserSettings,apiUtil.errorHandler);
// SSH keys
var sshkeys = require("./sshkeys");
sshkeys.init(runtime);
editorApp.use("/settings/user/keys",sshkeys.app());
return editorApp;
}

125
red/api/editor/sshkeys.js Normal file
View File

@ -0,0 +1,125 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var express = require("express");
var os = require("os");
var runtime;
var settings;
var needsPermission = require("../auth").needsPermission;
function getUsername(userObj) {
var username = os.hostname();
if ( userObj && userObj.name ) {
username = userObj.name;
}
return username;
}
module.exports = {
init: function(_runtime) {
runtime = _runtime;
settings = runtime.settings;
},
app: function() {
var app = express();
// SSH keys
// List all SSH keys
app.get("/", needsPermission("settings.read"), function(req,res) {
var username = getUsername(req.user);
runtime.storage.sshkeys.listSSHKeys(username)
.then(function(list) {
res.json({
keys: list
});
})
.catch(function(err) {
// console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
});
});
// Get SSH key detail
app.get("/:id", needsPermission("settings.read"), function(req,res) {
var username = getUsername(req.user);
console.log('username:', username);
runtime.storage.sshkeys.getSSHKey(username, req.params.id)
.then(function(data) {
res.json({
publickey: data
});
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
});
});
// Generate a SSH key
app.post("/", needsPermission("settings.write"), function(req,res) {
var username = getUsername(req.user);
console.log('req.body:', req.body);
if ( req.body && req.body.name ) {
runtime.storage.sshkeys.generateSSHKey(username, "", req.body.name, req.body)
.then(function(name) {
console.log('generate key --- success name:', name);
res.json({
name: name
});
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
});
}
else {
res.status(400).json({error:"unexpected_error", message:"You need to have body or body.name"});
}
});
// Delete a SSH key
app.delete("/:id", needsPermission("settings.write"), function(req,res) {
var username = getUsername(req.user);
runtime.storage.sshkeys.deleteSSHKey(username, req.params.id)
.then(function(ret) {
res.status(204).end();
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
});
});
return app;
}
}

View File

@ -57,6 +57,9 @@ var storageModuleInterface = {
if (storageModule.projects) {
storageModuleInterface.projects = storageModule.projects;
}
if (storageModule.sshkeys) {
storageModuleInterface.sshkeys = storageModule.sshkeys;
}
return storageModule.init(runtime.settings,runtime);
},
getFlows: function() {

View File

@ -24,6 +24,7 @@ var library = require("./library");
var sessions = require("./sessions");
var runtimeSettings = require("./settings");
var projects = require("./projects");
var sshkeys = require("./sshkeys");
var initialFlowLoadComplete = false;
var settings;
@ -60,6 +61,7 @@ var localfilesystem = {
runtimeSettings.init(settings);
promises.push(library.init(settings));
promises.push(projects.init(settings, runtime));
promises.push(sshkeys.init(settings, runtime));
var packageFile = fspath.join(settings.userDir,"package.json");
var packagePromise = when.resolve();
@ -94,7 +96,8 @@ var localfilesystem = {
saveSessions: sessions.saveSessions,
getLibraryEntry: library.getLibraryEntry,
saveLibraryEntry: library.saveLibraryEntry,
projects: projects
projects: projects,
sshkeys: sshkeys
};
module.exports = localfilesystem;

View File

@ -0,0 +1,152 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var fs = require('fs-extra');
var when = require('when');
var fspath = require("path");
var keygen = require('ssh-keygen');
var settings;
var runtime;
var log;
var sshkeyDir;
function init(_settings, _runtime) {
settings = _settings;
runtime = _runtime;
log = runtime.log;
sshkeyDir = fspath.join(settings.userDir, "projects", ".sshkeys");
// console.log('sshkeys.init()');
return createSSHKeyDirectory();
}
function createSSHKeyDirectory() {
return fs.ensureDir(sshkeyDir);
}
function listSSHKeys(username) {
var startStr = username + '_';
// console.log('sshkeyDir:', sshkeyDir);
return fs.readdir(sshkeyDir).then(function(fns) {
var ret = fns.sort()
.filter(function(fn) {
var fullPath = fspath.join(sshkeyDir,fn);
if (fn[0] != ".") {
var stats = fs.lstatSync(fullPath);
if (stats.isFile()) {
return fn.startsWith(startStr);
}
}
return false;
})
.map(function(filename) {
return filename.substr(startStr.length);
})
.reduce(function(prev, current) {
var parsePath = fspath.parse(current);
if ( parsePath ) {
if ( parsePath.ext !== '.pub' ) {
// Private Keys
prev.keyFiles.push(parsePath.base);
}
else if ( parsePath.ext === '.pub' && (prev.keyFiles.some(function(elem){ return elem === parsePath.name; }))) {
prev.privateKeyFiles.push(parsePath.name);
}
}
return prev;
}, { keyFiles: [], privateKeyFiles: [] });
return ret.privateKeyFiles.map(function(filename) {
return {
name: filename
};
});
});
}
function getSSHKey(username, name) {
return checkSSHKeyFileAndGetPublicKeyFileName(username, name)
.then(function(publicSSHKeyPath) {
return fs.readFile(publicSSHKeyPath, 'utf-8');
});
}
function generateSSHKey(username, email, name, data) {
var sshKeyFileBasename = username + '_' + name;
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
return generateSSHKeyPair(privateKeyFilePath, email, data.password, data.size)
.then(function() {
return name;
});
}
function deleteSSHKey(username, name) {
return checkSSHKeyFileAndGetPublicKeyFileName(username, name)
.then(function() {
return deleteSSHKeyFiles(username, name);
});
}
function checkSSHKeyFileAndGetPublicKeyFileName(username, name) {
var sshKeyFileBasename = username + '_' + name;
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
return when.all([
fs.access(privateKeyFilePath, (fs.constants || fs).R_OK),
fs.access(publicKeyFilePath , (fs.constants || fs).R_OK)
])
.then(function() {
return when.resolve(publicKeyFilePath);
});
}
function deleteSSHKeyFiles(username, name) {
var sshKeyFileBasename = username + '_' + name;
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
return when.all([
fs.remove(privateKeyFilePath),
fs.remove(publicKeyFilePath)
])
.then(function(retArray) {
return when.resolve(true);
});
}
function generateSSHKeyPair(privateKeyPath, comment, password, size) {
return when.promise(function(resolve, reject) {
keygen({
location: privateKeyPath,
comment: comment,
password: password,
size: size
}, function(err, out) {
if ( err ) {
reject(err);
}
else {
resolve();
}
});
});
}
module.exports = {
init: init,
listSSHKeys: listSSHKeys,
getSSHKey: getSSHKey,
generateSSHKey: generateSSHKey,
deleteSSHKey: deleteSSHKey
};

View File

@ -0,0 +1,377 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var sinon = require("sinon");
var request = require("supertest");
var express = require("express");
var editorApi = require("../../../../red/api/editor");
var comms = require("../../../../red/api/editor/comms");
var info = require("../../../../red/api/editor/settings");
var auth = require("../../../../red/api/auth");
var sshkeys = require("../../../../red/api/editor/sshkeys");
var when = require("when");
var bodyParser = require("body-parser");
var fs = require("fs-extra");
var fspath = require("path");
describe("api/editor/sshkeys", function() {
var app;
var mockList = [
'library','theme','locales','credentials','comms'
]
var isStarted = true;
var errors = [];
var session_data = {};
// before(function() {
// mockList.forEach(function(m) {
// sinon.stub(require("../../../../../red/api/editor/"+m),"init",function(){});
// });
// sinon.stub(require("../../../../../red/api/editor/theme"),"app",function(){ return express()});
// });
// after(function() {
// mockList.forEach(function(m) {
// require("../../../../../red/api/editor/"+m).init.restore();
// })
// require("../../../../../red/api/editor/theme").app.restore();
// });
var mockRuntime = {
settings:{
httpNodeRoot: true,
httpAdminRoot: true,
disableEditor: false,
exportNodeSettings:function(){},
// adminAuth: {
// default: {
// permissions: ['*']
// }
// },
storage: {
getSessions: function(){
return when.resolve(session_data);
},
setSessions: function(_session) {
session_data = _session;
return when.resolve();
}
},
log:{audit:function(){},error:function(msg){errors.push(msg)}}
},
storage: {
sshkeys: {
init: function(){},
listSSHKeys: function(){},
getSSHKey: function(){},
generateSSHKey: function(){},
deleteSSHKey: function(){},
}
},
events:{on:function(){},removeListener:function(){}},
isStarted: function() { return isStarted; },
nodes: {paletteEditorEnabled: function() { return false }}
};
before(function() {
auth.init(mockRuntime);
app = express();
app.use(bodyParser.json());
app.use(editorApi.init({},mockRuntime));
});
after(function() {
// fs.removeSync()
})
beforeEach(function() {
sinon.stub(mockRuntime.storage.sshkeys, "listSSHKeys");
sinon.stub(mockRuntime.storage.sshkeys, "getSSHKey");
sinon.stub(mockRuntime.storage.sshkeys, "generateSSHKey");
sinon.stub(mockRuntime.storage.sshkeys, "deleteSSHKey");
})
afterEach(function() {
mockRuntime.storage.sshkeys.listSSHKeys.restore();
mockRuntime.storage.sshkeys.getSSHKey.restore();
mockRuntime.storage.sshkeys.generateSSHKey.restore();
mockRuntime.storage.sshkeys.deleteSSHKey.restore();
})
it('GET /settings/user/keys --- return empty list', function(done) {
mockRuntime.storage.sshkeys.listSSHKeys.returns(Promise.resolve([]));
request(app)
.get("/settings/user/keys")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('keys');
res.body.keys.should.be.empty();
done();
});
});
it('GET /settings/user/keys --- return normal list', function(done) {
var fileList = [
'test_key01',
'test_key02'
];
var retList = fileList.map(function(elem) {
return {
name: elem
};
});
mockRuntime.storage.sshkeys.listSSHKeys.returns(Promise.resolve(retList));
request(app)
.get("/settings/user/keys")
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('keys');
for (var item of retList) {
res.body.keys.should.containEql(item);
}
done();
});
});
it('GET /settings/user/keys --- return Error', function(done) {
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
mockRuntime.storage.sshkeys.listSSHKeys.returns(Promise.reject(errInstance));
request(app)
.get("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal(errInstance.code);
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('GET /settings/user/keys --- return Unexpected Error', function(done) {
var errInstance = new Error("Messages.....");
// errInstance.code = "test_code";
mockRuntime.storage.sshkeys.listSSHKeys.returns(Promise.reject(errInstance));
request(app)
.get("/settings/user/keys")
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return content', function(done) {
var key_file_name = "test_key";
var fileContent = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3a+sgtgzSbbliWxmOq5p6+H/mE+0gjWfLWrkIVmHENd1mifV4uCmIHAR2NfuadUYMQ3+bQ90kpmmEKTMYPsyentsKpHQZxTzG7wOCAIpJnbPTHDMxEJhVTaAwEjbVyMSIzTTPfnhoavWIBu0+uMgKDDlBm+RjlgkFlyhXyCN6UwFrIUUMH6Gw+eQHLiooKIl8ce7uDxIlt+9b7hFCU+sQ3kvuse239DZluu6+8buMWqJvrEHgzS9adRFKku8nSPAEPYn85vDi7OgVAcLQufknNgs47KHBAx9h04LeSrFJ/P5J1b//ItRpMOIme+O9d1BR46puzhvUaCHLdvO9czj+OmW+dIm+QIk6lZIOOMnppG72kZxtLfeKT16ur+2FbwAdL9ItBp4BI/YTlBPoa5mLMxpuWfmX1qHntvtGc9wEwS1P7YFfmF3XiK5apxalzrn0Qlr5UmDNbVIqJb1OlbC0w03Z0oktti1xT+R2DGOLWM4lBbpXDHV1BhQ7oYOvbUD8Cnof55lTP0WHHsOHlQc/BGDti1XA9aBX/OzVyzBUYEf0pkimsD0RYo6aqt7QwehJYdlz9x1NBguBffT0s4NhNb9IWr+ASnFPvNl2sw4XH/8U0J0q8ZkMpKkbLM1Zdp1Fv00GF0f5UNRokai6uM3w/ccantJ3WvZ6GtctqytWrw== \n";
mockRuntime.storage.sshkeys.getSSHKey.returns(Promise.resolve(fileContent));
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
mockRuntime.storage.sshkeys.getSSHKey.called.should.be.true();
res.body.should.be.deepEqual({ publickey: fileContent });
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
mockRuntime.storage.sshkeys.getSSHKey.returns(Promise.reject(errInstance));
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal(errInstance.code);
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('GET /settings/user/keys/<key_file_name> --- return Unexpected Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
// errInstance.code = "test_code";
mockRuntime.storage.sshkeys.getSSHKey.returns(Promise.reject(errInstance));
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
it('POST /settings/user/keys --- success', function(done) {
var key_file_name = "test_key";
mockRuntime.storage.sshkeys.generateSSHKey.returns(Promise.resolve(key_file_name));
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(200)
.end(function(err,res) {
if (err) {
return done(err);
}
done();
});
});
it('POST /settings/user/keys --- return parameter error', function(done) {
var key_file_name = "test_key";
mockRuntime.storage.sshkeys.generateSSHKey.returns(Promise.resolve(key_file_name));
request(app)
.post("/settings/user/keys")
// .send({ name: key_file_name })
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal("You need to have body or body.name");
done();
});
});
it('POST /settings/user/keys --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
mockRuntime.storage.sshkeys.generateSSHKey.returns(Promise.reject(errInstance));
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("test_code");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('POST /settings/user/keys --- return Unexpected error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
// errInstance.code = "test_code";
mockRuntime.storage.sshkeys.generateSSHKey.returns(Promise.reject(errInstance));
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- success', function(done) {
var key_file_name = "test_key";
mockRuntime.storage.sshkeys.deleteSSHKey.returns(Promise.resolve(true));
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(204)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.be.true();
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- return Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
errInstance.code = "test_code";
mockRuntime.storage.sshkeys.deleteSSHKey.returns(Promise.reject(errInstance));
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("test_code");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.message);
done();
});
});
it('DELETE /settings/user/keys/<key_file_name> --- return Unexpected Error', function(done) {
var key_file_name = "test_key";
var errInstance = new Error("Messages.....");
// errInstance.code = "test_code";
mockRuntime.storage.sshkeys.deleteSSHKey.returns(Promise.reject(errInstance));
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property('error');
res.body.error.should.be.equal("unexpected_error");
res.body.should.have.property('message');
res.body.message.should.be.equal(errInstance.toString());
done();
});
});
});

View File

@ -0,0 +1,422 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var should = require("should");
var fs = require('fs-extra');
var path = require('path');
var localfilesystem = require("../../../../../red/runtime/storage/localfilesystem");
var sshkeys = require("../../../../../red/runtime/storage/localfilesystem/sshkeys");
describe("storage/localfilesystem/sshkeys", function() {
var userDir = path.join(__dirname,".testSSHKeyUserHome");
var mockSettings = {
userDir: userDir
};
var mockRuntime = {
log:{
_:function() { return "placeholder message"},
info: function() { }
}
};
beforeEach(function(done) {
fs.remove(userDir,function(err) {
fs.mkdir(userDir,done);
});
});
afterEach(function(done) {
fs.remove(userDir,done);
});
it('should create sshkey directory when sshkey initializes', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
var ret = fs.existsSync(sshkeyDirPath);
fs.existsSync(sshkeyDirPath).should.be.true();
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should get sshkey empty list if there is no sshkey file', function(done) {
var username = 'test';
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
sshkeys.listSSHKeys(username).then(function(retObj) {
console.log('retObj:', retObj);
retObj.should.be.instanceOf(Array).and.have.lengthOf(0);
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should get sshkey list', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var filenameList = ['test-key01', 'test-key02'];
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
for(var filename of filenameList) {
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),"","utf8");
}
sshkeys.listSSHKeys(username).then(function(retObj) {
retObj.should.be.instanceOf(Array).and.have.lengthOf(filenameList.length);
for(var filename of filenameList) {
retObj.should.containEql({ name: filename });
}
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should not get sshkey file if there is only private key', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var filenameList = ['test-key01', 'test-key02'];
var onlyPrivateKeyFilenameList = ['test-key03', 'test-key04'];
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
for(var filename of filenameList) {
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),"","utf8");
}
for(var filename of onlyPrivateKeyFilenameList) {
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
}
sshkeys.listSSHKeys(username).then(function(retObj) {
retObj.should.be.instanceOf(Array).and.have.lengthOf(filenameList.length);
for(var filename of filenameList) {
retObj.should.containEql({ name: filename });
}
for(var filename of onlyPrivateKeyFilenameList) {
retObj.should.not.containEql({ name: filename });
}
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should not get sshkey file if there is only public key', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var filenameList = ['test-key01', 'test-key02'];
var directoryList = ['test-key03', '.test-key04'];
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
for(var filename of filenameList) {
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),"","utf8");
}
for(var filename of directoryList) {
fs.ensureDirSync(path.join(sshkeyDirPath,filename));
}
sshkeys.listSSHKeys(username).then(function(retObj) {
retObj.should.be.instanceOf(Array).and.have.lengthOf(filenameList.length);
for(var filename of filenameList) {
retObj.should.containEql({ name: filename });
}
for(var directoryname of directoryList) {
retObj.should.not.containEql({ name: directoryname });
}
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should get sshkey list that does not have directory', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var otherUsername = 'other';
var filenameList = ['test-key01', 'test-key02'];
var otherUserFilenameList = ['test-key03', 'test-key04'];
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
for(var filename of filenameList) {
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),"","utf8");
}
for(var filename of otherUserFilenameList) {
fs.writeFileSync(path.join(sshkeyDirPath,otherUsername+"_"+filename),"","utf8");
fs.writeFileSync(path.join(sshkeyDirPath,otherUsername+"_"+filename+".pub"),"","utf8");
}
sshkeys.listSSHKeys(username).then(function(retObj) {
retObj.should.be.instanceOf(Array).and.have.lengthOf(filenameList.length);
for(var filename of filenameList) {
retObj.should.containEql({ name: filename });
}
for(var filename of otherUserFilenameList) {
retObj.should.not.containEql({ name: filename });
}
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should get sshkey list that have keys of specified user', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var otherUsername = 'other';
var filenameList = ['test-key01', 'test-key02'];
var otherUserFilenameList = ['test-key03', 'test-key04'];
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
for(var filename of filenameList) {
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),"","utf8");
}
for(var filename of otherUserFilenameList) {
fs.writeFileSync(path.join(sshkeyDirPath,otherUsername+"_"+filename),"","utf8");
fs.writeFileSync(path.join(sshkeyDirPath,otherUsername+"_"+filename+".pub"),"","utf8");
}
sshkeys.listSSHKeys(username).then(function(retObj) {
retObj.should.be.instanceOf(Array).and.have.lengthOf(filenameList.length);
for(var filename of filenameList) {
retObj.should.containEql({ name: filename });
}
for(var filename of otherUserFilenameList) {
retObj.should.not.containEql({ name: filename });
}
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should generate sshkey file with empty data', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var email = 'test@test.com';
var filename = 'test-key01';
var data = {};
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
sshkeys.generateSSHKey(username, email, filename, data).then(function(retObj) {
retObj.should.be.equal(filename);
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename)).should.be.true();
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename+'.pub')).should.be.true();
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should generate sshkey file with password data', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var email = 'test@test.com';
var filename = 'test-key01';
var data = {
password: 'testtest'
};
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
sshkeys.generateSSHKey(username, email, filename, data).then(function(retObj) {
retObj.should.be.equal(filename);
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename)).should.be.true();
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename+'.pub')).should.be.true();
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should generate sshkey file with size data', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var email = 'test@test.com';
var filename = 'test-key01';
var data = {
size: 4096
};
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
sshkeys.generateSSHKey(username, email, filename, data).then(function(retObj) {
retObj.should.be.equal(filename);
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename)).should.be.true();
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename+'.pub')).should.be.true();
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should generate sshkey file with password & size data', function(done) {
this.timeout(5000);
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var email = 'test@test.com';
var filename = 'test-key01';
var data = {
password: 'testtest',
size: 4096
};
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
sshkeys.generateSSHKey(username, email, filename, data).then(function(retObj) {
retObj.should.be.equal(filename);
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename)).should.be.true();
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename+'.pub')).should.be.true();
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should not generate sshkey file with illegal size data', function(done) {
this.timeout(5000);
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var email = 'test@test.com';
var filename = 'test-key01';
var data = {
size: 3333
};
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
sshkeys.generateSSHKey(username, email, filename, data).then(function(retObj) {
retObj.should.be.equal(filename);
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename)).should.be.true();
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename+'.pub')).should.be.true();
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should get sshkey file content', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var filename = 'test-key01';
var fileContent = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3a+sgtgzSbbliWxmOq5p6+H/mE+0gjWfLWrkIVmHENd1mifV4uCmIHAR2NfuadUYMQ3+bQ90kpmmEKTMYPsyentsKpHQZxTzG7wOCAIpJnbPTHDMxEJhVTaAwEjbVyMSIzTTPfnhoavWIBu0+uMgKDDlBm+RjlgkFlyhXyCN6UwFrIUUMH6Gw+eQHLiooKIl8ce7uDxIlt+9b7hFCU+sQ3kvuse239DZluu6+8buMWqJvrEHgzS9adRFKku8nSPAEPYn85vDi7OgVAcLQufknNgs47KHBAx9h04LeSrFJ/P5J1b//ItRpMOIme+O9d1BR46puzhvUaCHLdvO9czj+OmW+dIm+QIk6lZIOOMnppG72kZxtLfeKT16ur+2FbwAdL9ItBp4BI/YTlBPoa5mLMxpuWfmX1qHntvtGc9wEwS1P7YFfmF3XiK5apxalzrn0Qlr5UmDNbVIqJb1OlbC0w03Z0oktti1xT+R2DGOLWM4lBbpXDHV1BhQ7oYOvbUD8Cnof55lTP0WHHsOHlQc/BGDti1XA9aBX/OzVyzBUYEf0pkimsD0RYo6aqt7QwehJYdlz9x1NBguBffT0s4NhNb9IWr+ASnFPvNl2sw4XH/8U0J0q8ZkMpKkbLM1Zdp1Fv00GF0f5UNRokai6uM3w/ccantJ3WvZ6GtctqytWrw== \n";
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),fileContent,"utf8");
sshkeys.getSSHKey(username, filename).then(function(retObj) {
retObj.should.be.equal(fileContent);
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
it('should delete sshkey files', function(done) {
var sshkeyDirPath = path.join(userDir, 'projects', '.sshkeys');
var username = 'test';
var filename = 'test-key01';
var fileContent = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQD3a+sgtgzSbbliWxmOq5p6+H/mE+0gjWfLWrkIVmHENd1mifV4uCmIHAR2NfuadUYMQ3+bQ90kpmmEKTMYPsyentsKpHQZxTzG7wOCAIpJnbPTHDMxEJhVTaAwEjbVyMSIzTTPfnhoavWIBu0+uMgKDDlBm+RjlgkFlyhXyCN6UwFrIUUMH6Gw+eQHLiooKIl8ce7uDxIlt+9b7hFCU+sQ3kvuse239DZluu6+8buMWqJvrEHgzS9adRFKku8nSPAEPYn85vDi7OgVAcLQufknNgs47KHBAx9h04LeSrFJ/P5J1b//ItRpMOIme+O9d1BR46puzhvUaCHLdvO9czj+OmW+dIm+QIk6lZIOOMnppG72kZxtLfeKT16ur+2FbwAdL9ItBp4BI/YTlBPoa5mLMxpuWfmX1qHntvtGc9wEwS1P7YFfmF3XiK5apxalzrn0Qlr5UmDNbVIqJb1OlbC0w03Z0oktti1xT+R2DGOLWM4lBbpXDHV1BhQ7oYOvbUD8Cnof55lTP0WHHsOHlQc/BGDti1XA9aBX/OzVyzBUYEf0pkimsD0RYo6aqt7QwehJYdlz9x1NBguBffT0s4NhNb9IWr+ASnFPvNl2sw4XH/8U0J0q8ZkMpKkbLM1Zdp1Fv00GF0f5UNRokai6uM3w/ccantJ3WvZ6GtctqytWrw== \n";
localfilesystem.init(mockSettings, mockRuntime).then(function() {
sshkeys.init(mockSettings, mockRuntime).then(function() {
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename),"","utf8");
fs.writeFileSync(path.join(sshkeyDirPath,username+"_"+filename+".pub"),fileContent,"utf8");
sshkeys.deleteSSHKey(username, filename).then(function(retObj) {
retObj.should.be.true();
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename)).should.be.false();
fs.existsSync(path.join(sshkeyDirPath,username+'_'+filename+'.pub')).should.be.false();
done();
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
}).catch(function(err) {
done(err);
});
});
});