diff --git a/red/runtime/storage/localfilesystem/projects/Project.js b/red/runtime/storage/localfilesystem/projects/Project.js index 0d5939673..46817d397 100644 --- a/red/runtime/storage/localfilesystem/projects/Project.js +++ b/red/runtime/storage/localfilesystem/projects/Project.js @@ -968,6 +968,7 @@ function init(_settings, _runtime) { runtime = _runtime; log = runtime.log; projectsDir = fspath.join(settings.userDir,"projects"); + authCache.init(); } module.exports = { diff --git a/red/runtime/storage/localfilesystem/projects/git/authCache.js b/red/runtime/storage/localfilesystem/projects/git/authCache.js index d2c3d6720..678047613 100644 --- a/red/runtime/storage/localfilesystem/projects/git/authCache.js +++ b/red/runtime/storage/localfilesystem/projects/git/authCache.js @@ -17,6 +17,9 @@ var authCache = {} module.exports = { + init: function() { + authCache = {}; + }, clear: function(project,remote, user) { if (user && remote && authCache[project] && authCache[project][remote]) { delete authCache[project][remote][user]; diff --git a/red/runtime/storage/localfilesystem/projects/git/authServer.js b/red/runtime/storage/localfilesystem/projects/git/authServer.js index a4ab209e8..3e04e2c3c 100644 --- a/red/runtime/storage/localfilesystem/projects/git/authServer.js +++ b/red/runtime/storage/localfilesystem/projects/git/authServer.js @@ -36,7 +36,7 @@ function getListenPath() { var ResponseServer = function(auth) { return new Promise(function(resolve, reject) { - server = net.createServer(function(connection) { + var server = net.createServer(function(connection) { connection.setEncoding('utf8'); var parts = []; connection.on('data', function(data) { @@ -81,7 +81,7 @@ var ResponseServer = function(auth) { var ResponseSSHServer = function(auth) { return new Promise(function(resolve, reject) { - server = net.createServer(function(connection) { + var server = net.createServer(function(connection) { connection.setEncoding('utf8'); var parts = []; connection.on('data', function(data) { @@ -93,6 +93,7 @@ var ResponseSSHServer = function(auth) { console.log("LINE:",line); parts = []; if (line==='The') { + // TODO: document these exchanges! connection.end('yes'); // server.close(); } else if (line === 'Enter') { diff --git a/test/red/runtime/storage/localfilesystem/projects/Project_spec.js b/test/red/runtime/storage/localfilesystem/projects/Project_spec.js new file mode 100644 index 000000000..952e4421d --- /dev/null +++ b/test/red/runtime/storage/localfilesystem/projects/Project_spec.js @@ -0,0 +1,19 @@ +/** + * 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. + **/ + +describe("storage/localfilesystem/projects/Project", function() { + it.skip("NEEDS TESTS WRITING",function() {}); +}) diff --git a/test/red/runtime/storage/localfilesystem/projects/defaultFileSet_spec.js b/test/red/runtime/storage/localfilesystem/projects/defaultFileSet_spec.js new file mode 100644 index 000000000..d84a3de7f --- /dev/null +++ b/test/red/runtime/storage/localfilesystem/projects/defaultFileSet_spec.js @@ -0,0 +1,68 @@ +/** + * 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 defaultFileSet = require("../../../../../../red/runtime/storage/localfilesystem/projects/defaultFileSet"); + +describe('storage/localfilesystem/projects/defaultFileSet', function() { + it('generates package.json for a project', function() { + var generated = defaultFileSet["package.json"]({ + name: "A TEST NAME", + summary: "A TEST SUMMARY", + files: { + flow: "MY FLOW FILE", + credentials: "MY CREDENTIALS FILE" + } + }); + + var parsed = JSON.parse(generated); + parsed.should.have.property('name',"A TEST NAME"); + parsed.should.have.property('description',"A TEST SUMMARY"); + parsed.should.have.property('node-red'); + parsed['node-red'].should.have.property('settings'); + parsed['node-red'].settings.should.have.property('flowFile',"MY FLOW FILE"); + parsed['node-red'].settings.should.have.property('credentialsFile',"MY CREDENTIALS FILE"); + }); + + it('generates README.md for a project', function() { + var generated = defaultFileSet["README.md"]({ + name: "A TEST NAME", + summary: "A TEST SUMMARY" + }); + generated.should.match(/A TEST NAME/); + generated.should.match(/A TEST SUMMARY/); + }); + it('generates settings.json for a project', function() { + var generated = defaultFileSet["settings.json"]({ + name: "A TEST NAME", + summary: "A TEST SUMMARY" + }); + generated.length.should.be.greaterThan(0); + }); + it('generates .gitignore for a project', function() { + var generated = defaultFileSet[".gitignore"]({ + name: "A TEST NAME", + summary: "A TEST SUMMARY" + }); + generated.length.should.be.greaterThan(0); + }); + + + + + +}); diff --git a/test/red/runtime/storage/localfilesystem/projects/git/authCache_spec.js b/test/red/runtime/storage/localfilesystem/projects/git/authCache_spec.js new file mode 100644 index 000000000..8e182cea2 --- /dev/null +++ b/test/red/runtime/storage/localfilesystem/projects/git/authCache_spec.js @@ -0,0 +1,83 @@ +/** + * 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 authCache = require("../../../../../../../red/runtime/storage/localfilesystem/projects/git/authCache") + +describe("localfilesystem/projects/git/authCache", function() { + + beforeEach(function() { + authCache.init(); + }); + afterEach(function() { + authCache.init(); + }); + + it('sets/clears auth details for a given project/remote/user', function() { + should.not.exist(authCache.get("project","remote1","user1")); + should.not.exist(authCache.get("project","remote1","user2")); + + authCache.set("project","remote1","user1",{foo1:"bar1"}); + authCache.set("project","remote1","user2",{foo2:"bar2"}); + + var result = authCache.get("project","remote1","user1"); + result.should.have.property("foo1","bar1"); + + result = authCache.get("project","remote1","user2"); + result.should.have.property("foo2","bar2"); + + authCache.clear("project","remote1","user1"); + should.not.exist(authCache.get("project","remote1","user1")); + should.exist(authCache.get("project","remote1","user2")); + + }); + + + it('clears auth details for all users on a given project/remote', function() { + + authCache.set("project","remote1","user1",{foo1:"bar1"}); + authCache.set("project","remote1","user2",{foo2:"bar2"}); + authCache.set("project","remote2","user1",{foo3:"bar3"}); + + should.exist(authCache.get("project","remote1","user1")); + should.exist(authCache.get("project","remote1","user2")); + should.exist(authCache.get("project","remote2","user1")); + + authCache.clear("project","remote1"); + should.not.exist(authCache.get("project","remote1","user1")); + should.not.exist(authCache.get("project","remote1","user2")); + should.exist(authCache.get("project","remote2","user1")); + }); + + it('clears auth details for all remotes/users on a given project', function() { + + authCache.set("project1","remote1","user1",{foo1:"bar1"}); + authCache.set("project1","remote1","user2",{foo2:"bar2"}); + authCache.set("project2","remote2","user1",{foo3:"bar3"}); + + should.exist(authCache.get("project1","remote1","user1")); + should.exist(authCache.get("project1","remote1","user2")); + should.exist(authCache.get("project2","remote2","user1")); + + authCache.clear("project2"); + should.exist(authCache.get("project1","remote1","user1")); + should.exist(authCache.get("project1","remote1","user2")); + should.not.exist(authCache.get("project2","remote2","user1")); + }); + +}); diff --git a/test/red/runtime/storage/localfilesystem/projects/git/authServer_spec.js b/test/red/runtime/storage/localfilesystem/projects/git/authServer_spec.js new file mode 100644 index 000000000..11cc15ca7 --- /dev/null +++ b/test/red/runtime/storage/localfilesystem/projects/git/authServer_spec.js @@ -0,0 +1,73 @@ +/** + * 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 net = require("net"); +var path = require("path"); +var os = require("os"); +var should = require("should"); +var sinon = require("sinon"); +var child_process = require("child_process"); +var fs = require("fs-extra"); + +var authServer = require("../../../../../../../red/runtime/storage/localfilesystem/projects/git/authServer"); + + +var sendPrompt = function(localPath, prompt) { + return new Promise(function(resolve,reject) { + var response; + var socket = net.connect(localPath, function() { + socket.on('data', function(data) { response = data; socket.end() }); + socket.on('end', function() { + resolve(response); + }); + socket.on('error',reject); + socket.write(prompt+"\n", 'utf8'); + }); + socket.setEncoding('utf8'); + }); +} + + +describe("localfilesystem/projects/git/authServer", function() { + it("listens for user/pass prompts and returns provided auth", function(done) { + authServer.ResponseServer({username: "TEST_USER", password: "TEST_PASS"}).then(function(rs) { + sendPrompt(rs.path,"Username").then(function(response) { + response.should.eql("TEST_USER"); + return sendPrompt(rs.path,"Password"); + }).then(function(response) { + response.should.eql("TEST_PASS"); + }).then(done).catch(function(err) { + done(err); + }) + + }) + }); + + it("listens for ssh prompts and returns provided auth", function(done) { + authServer.ResponseSSHServer({passphrase: "TEST_PASSPHRASE"}).then(function(rs) { + sendPrompt(rs.path,"The").then(function(response) { + // TODO: + response.should.eql("yes"); + return sendPrompt(rs.path,"Enter"); + }).then(function(response) { + response.should.eql("TEST_PASSPHRASE"); + }).then(done).catch(function(err) { + done(err); + }) + + }) + }) +}); diff --git a/test/red/runtime/storage/localfilesystem/projects/git/authWriter_spec.js b/test/red/runtime/storage/localfilesystem/projects/git/authWriter_spec.js new file mode 100644 index 000000000..71fb52a4e --- /dev/null +++ b/test/red/runtime/storage/localfilesystem/projects/git/authWriter_spec.js @@ -0,0 +1,80 @@ +/** + * 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 net = require("net"); +var path = require("path"); +var os = require("os"); +var should = require("should"); +var sinon = require("sinon"); +var child_process = require("child_process"); +var fs = require("fs-extra"); + +var authWriter = "../../../../../../../red/runtime/storage/localfilesystem/projects/git/authWriter"; + +function getListenPath() { + var seed = (0x100000+Math.random()*0x999999).toString(16); + var fn = 'node-red-git-askpass-'+seed+'-sock'; + var listenPath; + if (process.platform === 'win32') { + listenPath = '\\\\.\\pipe\\'+getListenPath; + } else { + listenPath = path.join(process.env['XDG_RUNTIME_DIR'] || os.tmpdir(), fn); + } + // console.log(listenPath); + return listenPath; +} + + +describe("localfilesystem/projects/git/authWriter", function() { + it("connects to port and sends passphrase", function(done) { + var receivedData = ""; + var server = net.createServer(function(connection) { + connection.setEncoding('utf8'); + connection.on('data', function(data) { + receivedData += data; + var m = data.indexOf("\n"); + if (m !== -1) { + connection.end(); + } + }); + }); + + var listenPath = getListenPath(); + + server.listen(listenPath, function(ready) { + child_process.exec(process.execPath+' "'+authWriter+'" "'+listenPath+'" TEST_PHRASE_FOO',{cwd:__dirname}, (error,stdout,stderr) => { + server.close(); + try { + receivedData.should.eql("TEST_PHRASE_FOO\n"); + done(); + } catch(err) { + done(err); + } + }); + }); + server.on('close', function() { + // console.log("Closing response server"); + fs.removeSync(listenPath); + }); + server.on('error',function(err) { + console.log("ResponseServer unexpectedError:",err.toString()); + server.close(); + done(err); + }); + + + }) +}); diff --git a/test/red/runtime/storage/localfilesystem/projects/git/index_spec.js b/test/red/runtime/storage/localfilesystem/projects/git/index_spec.js index e69de29bb..34cf8e366 100644 --- a/test/red/runtime/storage/localfilesystem/projects/git/index_spec.js +++ b/test/red/runtime/storage/localfilesystem/projects/git/index_spec.js @@ -0,0 +1,19 @@ +/** + * 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. + **/ + +describe("storage/localfilesystem/projects/git/index", function() { + it.skip("NEEDS TESTS WRITING",function() {}); +}) diff --git a/test/red/runtime/storage/localfilesystem/projects/index_spec.js b/test/red/runtime/storage/localfilesystem/projects/index_spec.js index e69de29bb..d70c555c3 100644 --- a/test/red/runtime/storage/localfilesystem/projects/index_spec.js +++ b/test/red/runtime/storage/localfilesystem/projects/index_spec.js @@ -0,0 +1,19 @@ +/** + * 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. + **/ + +describe("storage/localfilesystem/projects/index", function() { + it.skip("NEEDS TESTS WRITING",function() {}); +}) diff --git a/test/red/runtime/storage/localfilesystem/projects/ssh_spec.js b/test/red/runtime/storage/localfilesystem/projects/ssh_spec.js new file mode 100644 index 000000000..c71d83268 --- /dev/null +++ b/test/red/runtime/storage/localfilesystem/projects/ssh_spec.js @@ -0,0 +1,129 @@ +/** + * 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 child_process = require('child_process'); +var EventEmitter = require("events"); + +var ssh = require("../../../../../../red/runtime/storage/localfilesystem/projects/ssh") + +describe("localfilesystem/projects/ssh", function() { + + afterEach(function() { + child_process.spawn.restore(); + }) + + it("invokes sshkeygen", function(done) { + var command; + var args; + var opts; + sinon.stub(child_process,"spawn", function(_command,_args,_opts) { + _command = command; + _args = args; + _opts = opts; + + var e = new EventEmitter(); + e.stdout = new EventEmitter(); + e.stderr = new EventEmitter(); + setTimeout(function() { + e.stdout.emit("data","result"); + e.emit("close",0); + },50) + return e; + }); + + ssh.generateKey({ + size: 123, + location: 'location', + comment: 'comment', + password: 'password' + }).then(function(output) { + output.should.equal("result"); + done(); + }).catch(function(err) { + done(err); + }) + }) + + it("reports passphrase too short", function(done) { + var command; + var args; + var opts; + sinon.stub(child_process,"spawn", function(_command,_args,_opts) { + _command = command; + _args = args; + _opts = opts; + + var e = new EventEmitter(); + e.stdout = new EventEmitter(); + e.stderr = new EventEmitter(); + setTimeout(function() { + e.stdout.emit("data","result"); + e.stderr.emit("data","passphrase is too short"); + e.emit("close",1); + },5) + return e; + }); + + ssh.generateKey({ + size: 123, + location: 'location', + comment: 'comment', + password: 'password' + }).then(function(output) { + done(new Error("Error not thrown")); + }).catch(function(err) { + err.should.have.property("code","key_passphrase_too_short"); + done(); + }) + }); + + it("reports key length too short", function(done) { + var command; + var args; + var opts; + sinon.stub(child_process,"spawn", function(_command,_args,_opts) { + _command = command; + _args = args; + _opts = opts; + + var e = new EventEmitter(); + e.stdout = new EventEmitter(); + e.stderr = new EventEmitter(); + setTimeout(function() { + e.stdout.emit("data","result"); + e.stderr.emit("data","Key must at least be 1024 bits"); + e.emit("close",1); + },50) + return e; + }); + + ssh.generateKey({ + size: 123, + location: 'location', + comment: 'comment', + password: 'password' + }).then(function(output) { + done(new Error("Error not thrown")); + }).catch(function(err) { + err.should.have.property("code","key_length_too_short"); + done(); + }) + }); + + +});