mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'dev' into pr_1789
This commit is contained in:
47
test/unit/@node-red/editor-api/lib/auth/clients_spec.js
Normal file
47
test/unit/@node-red/editor-api/lib/auth/clients_spec.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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 NR_TEST_UTILS = require("nr-test-utils");
|
||||
var Clients = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/clients");
|
||||
|
||||
describe("api/auth/clients", function() {
|
||||
it('finds the known editor client',function(done) {
|
||||
Clients.get("node-red-editor").then(function(client) {
|
||||
client.should.have.property("id","node-red-editor");
|
||||
client.should.have.property("secret","not_available");
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('finds the known admin client',function(done) {
|
||||
Clients.get("node-red-admin").then(function(client) {
|
||||
client.should.have.property("id","node-red-admin");
|
||||
client.should.have.property("secret","not_available");
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
it('returns null for unknown client',function(done) {
|
||||
Clients.get("unknown-client").then(function(client) {
|
||||
should.not.exist(client);
|
||||
done();
|
||||
}).catch(function(err) {
|
||||
done(err);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
217
test/unit/@node-red/editor-api/lib/auth/index_spec.js
Normal file
217
test/unit/@node-red/editor-api/lib/auth/index_spec.js
Normal file
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var sinon = require("sinon");
|
||||
|
||||
var passport = require("passport");
|
||||
|
||||
var NR_TEST_UTILS = require("nr-test-utils");
|
||||
|
||||
var auth = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth");
|
||||
var Users = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/users");
|
||||
var Tokens = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/tokens");
|
||||
var Permissions = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/permissions");
|
||||
|
||||
describe("api/auth/index",function() {
|
||||
|
||||
|
||||
|
||||
describe("ensureClientSecret", function() {
|
||||
before(function() {
|
||||
auth.init({},{})
|
||||
});
|
||||
it("leaves client_secret alone if not present",function(done) {
|
||||
var req = {
|
||||
body: {
|
||||
client_secret: "test_value"
|
||||
}
|
||||
};
|
||||
auth.ensureClientSecret(req,null,function() {
|
||||
req.body.should.have.a.property("client_secret","test_value");
|
||||
done();
|
||||
})
|
||||
});
|
||||
it("applies a default client_secret if not present",function(done) {
|
||||
var req = {
|
||||
body: { }
|
||||
};
|
||||
auth.ensureClientSecret(req,null,function() {
|
||||
req.body.should.have.a.property("client_secret","not_available");
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
describe("revoke", function() {
|
||||
it("revokes a token", function(done) {
|
||||
var revokeToken = sinon.stub(Tokens,"revoke",function() {
|
||||
return when.resolve();
|
||||
});
|
||||
|
||||
var req = { body: { token: "abcdef" } };
|
||||
|
||||
var res = { status: function(resp) {
|
||||
revokeToken.restore();
|
||||
|
||||
resp.should.equal(200);
|
||||
return {
|
||||
end: done
|
||||
}
|
||||
}};
|
||||
|
||||
auth.revoke(req,res);
|
||||
});
|
||||
});
|
||||
|
||||
describe("login", function() {
|
||||
beforeEach(function() {
|
||||
sinon.stub(Tokens,"init",function(){});
|
||||
sinon.stub(Users,"init",function(){});
|
||||
});
|
||||
afterEach(function() {
|
||||
Tokens.init.restore();
|
||||
Users.init.restore();
|
||||
});
|
||||
it("returns login details - credentials", function(done) {
|
||||
auth.init({adminAuth:{type:"credentials"}},{})
|
||||
auth.login(null,{json: function(resp) {
|
||||
resp.should.have.a.property("type","credentials");
|
||||
resp.should.have.a.property("prompts");
|
||||
resp.prompts.should.have.a.lengthOf(2);
|
||||
done();
|
||||
}});
|
||||
});
|
||||
it("returns login details - none", function(done) {
|
||||
auth.init({},{})
|
||||
auth.login(null,{json: function(resp) {
|
||||
resp.should.eql({});
|
||||
done();
|
||||
}});
|
||||
});
|
||||
it("returns login details - strategy", function(done) {
|
||||
auth.init({adminAuth:{type:"strategy",strategy:{label:"test-strategy",icon:"test-icon"}}},{})
|
||||
auth.login(null,{json: function(resp) {
|
||||
resp.should.have.a.property("type","strategy");
|
||||
resp.should.have.a.property("prompts");
|
||||
resp.prompts.should.have.a.lengthOf(1);
|
||||
resp.prompts[0].should.have.a.property("type","button");
|
||||
resp.prompts[0].should.have.a.property("label","test-strategy");
|
||||
resp.prompts[0].should.have.a.property("icon","test-icon");
|
||||
|
||||
done();
|
||||
}});
|
||||
});
|
||||
|
||||
});
|
||||
describe("needsPermission", function() {
|
||||
beforeEach(function() {
|
||||
sinon.stub(Tokens,"init",function(){});
|
||||
sinon.stub(Users,"init",function(){});
|
||||
});
|
||||
afterEach(function() {
|
||||
Tokens.init.restore();
|
||||
Users.init.restore();
|
||||
if (passport.authenticate.restore) {
|
||||
passport.authenticate.restore();
|
||||
}
|
||||
if (Permissions.hasPermission.restore) {
|
||||
Permissions.hasPermission.restore();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('no-ops if adminAuth not set', function(done) {
|
||||
sinon.stub(passport,"authenticate",function(scopes,opts) {
|
||||
return function(req,res,next) {
|
||||
}
|
||||
});
|
||||
auth.init({});
|
||||
var func = auth.needsPermission("foo");
|
||||
func({},{},function() {
|
||||
passport.authenticate.called.should.be.false();
|
||||
done();
|
||||
})
|
||||
});
|
||||
it('skips auth if req.user undefined', function(done) {
|
||||
sinon.stub(passport,"authenticate",function(scopes,opts) {
|
||||
return function(req,res,next) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
sinon.stub(Permissions,"hasPermission",function(perm) { return true });
|
||||
auth.init({adminAuth:{}});
|
||||
var func = auth.needsPermission("foo");
|
||||
func({user:null},{},function() {
|
||||
try {
|
||||
passport.authenticate.called.should.be.true();
|
||||
Permissions.hasPermission.called.should.be.false();
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it('passes for valid user permission', function(done) {
|
||||
sinon.stub(passport,"authenticate",function(scopes,opts) {
|
||||
return function(req,res,next) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
sinon.stub(Permissions,"hasPermission",function(perm) { return true });
|
||||
auth.init({adminAuth:{}});
|
||||
var func = auth.needsPermission("foo");
|
||||
func({user:true,authInfo: { scope: "read"}},{},function() {
|
||||
try {
|
||||
passport.authenticate.called.should.be.true();
|
||||
Permissions.hasPermission.called.should.be.true();
|
||||
Permissions.hasPermission.lastCall.args[0].should.eql("read");
|
||||
Permissions.hasPermission.lastCall.args[1].should.eql("foo");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
it('rejects for invalid user permission', function(done) {
|
||||
sinon.stub(passport,"authenticate",function(scopes,opts) {
|
||||
return function(req,res,next) {
|
||||
next();
|
||||
}
|
||||
});
|
||||
sinon.stub(Permissions,"hasPermission",function(perm) { return false });
|
||||
auth.init({adminAuth:{}});
|
||||
var func = auth.needsPermission("foo");
|
||||
func({user:true,authInfo: { scope: "read"}},{
|
||||
status: function(status) {
|
||||
return { end: function() {
|
||||
try {
|
||||
status.should.eql(401);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
}}
|
||||
}
|
||||
},function() {
|
||||
done(new Error("hasPermission unexpected passed"))
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
59
test/unit/@node-red/editor-api/lib/auth/permissions_spec.js
Normal file
59
test/unit/@node-red/editor-api/lib/auth/permissions_spec.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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 NR_TEST_UTILS = require("nr-test-utils");
|
||||
|
||||
var permissions = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/permissions");
|
||||
|
||||
describe("api/auth/permissions", function() {
|
||||
describe("hasPermission", function() {
|
||||
it('a user with no permissions',function() {
|
||||
permissions.hasPermission([],"*").should.be.false();
|
||||
});
|
||||
it('a user with global permissions',function() {
|
||||
permissions.hasPermission("*","read").should.be.true();
|
||||
permissions.hasPermission(["*"],"write").should.be.true();
|
||||
});
|
||||
it('a user with read permissions',function() {
|
||||
permissions.hasPermission(["read"],"read").should.be.true();
|
||||
permissions.hasPermission(["read"],"node.read").should.be.true();
|
||||
permissions.hasPermission(["read"],"write").should.be.false();
|
||||
permissions.hasPermission(["read"],"node.write").should.be.false();
|
||||
permissions.hasPermission(["*.read"],"read").should.be.true();
|
||||
permissions.hasPermission(["*.read"],"node.read").should.be.true();
|
||||
permissions.hasPermission(["*.read"],"write").should.be.false();
|
||||
permissions.hasPermission(["*.read"],"node.write").should.be.false();
|
||||
});
|
||||
it('a user with foo permissions',function() {
|
||||
permissions.hasPermission("foo","foo").should.be.true();
|
||||
});
|
||||
it('an array of permissions', function() {
|
||||
permissions.hasPermission(["*"],["foo.read","foo.write"]).should.be.true();
|
||||
permissions.hasPermission("read",["foo.read","foo.write"]).should.be.false();
|
||||
permissions.hasPermission("read",["foo.read","bar.read"]).should.be.true();
|
||||
permissions.hasPermission(["flows.read"],["flows.read"]).should.be.true();
|
||||
permissions.hasPermission(["flows.read"],["flows.write"]).should.be.false();
|
||||
permissions.hasPermission(["flows.read","nodes.write"],["flows.write"]).should.be.false();
|
||||
permissions.hasPermission(["flows.read","nodes.write"],["nodes.write"]).should.be.true();
|
||||
});
|
||||
it('permits an empty permission', function() {
|
||||
permissions.hasPermission("*","").should.be.true();
|
||||
permissions.hasPermission("read",[""]).should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
273
test/unit/@node-red/editor-api/lib/auth/strategies_spec.js
Normal file
273
test/unit/@node-red/editor-api/lib/auth/strategies_spec.js
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
var sinon = require('sinon');
|
||||
|
||||
var NR_TEST_UTILS = require("nr-test-utils");
|
||||
|
||||
var strategies = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/strategies");
|
||||
var Users = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/users");
|
||||
var Tokens = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/tokens");
|
||||
var Clients = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/clients");
|
||||
|
||||
describe("api/auth/strategies", function() {
|
||||
describe("Password Token Exchange", function() {
|
||||
var userAuthentication;
|
||||
afterEach(function() {
|
||||
if (userAuthentication) {
|
||||
userAuthentication.restore();
|
||||
userAuthentication = null;
|
||||
}
|
||||
});
|
||||
|
||||
it('Handles authentication failure',function(done) {
|
||||
userAuthentication = sinon.stub(Users,"authenticate",function(username,password) {
|
||||
return when.resolve(null);
|
||||
});
|
||||
|
||||
strategies.passwordTokenExchange({},"user","password","scope",function(err,token) {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
token.should.be.false();
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Handles scope overreach',function(done) {
|
||||
userAuthentication = sinon.stub(Users,"authenticate",function(username,password) {
|
||||
return when.resolve({username:"user",permissions:"read"});
|
||||
});
|
||||
|
||||
strategies.passwordTokenExchange({},"user","password","*",function(err,token) {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
token.should.be.false();
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('Creates new token on authentication success',function(done) {
|
||||
userAuthentication = sinon.stub(Users,"authenticate",function(username,password) {
|
||||
return when.resolve({username:"user",permissions:"*"});
|
||||
});
|
||||
var tokenDetails = {};
|
||||
var tokenCreate = sinon.stub(Tokens,"create",function(username,client,scope) {
|
||||
tokenDetails.username = username;
|
||||
tokenDetails.client = client;
|
||||
tokenDetails.scope = scope;
|
||||
return when.resolve({accessToken: "123456"});
|
||||
});
|
||||
|
||||
strategies.passwordTokenExchange({id:"myclient"},"user","password","read",function(err,token) {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
token.should.equal("123456");
|
||||
tokenDetails.should.have.property("username","user");
|
||||
tokenDetails.should.have.property("client","myclient");
|
||||
tokenDetails.should.have.property("scope","read");
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
} finally {
|
||||
tokenCreate.restore();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("Anonymous Strategy", function() {
|
||||
it('Succeeds if anon user enabled',function(done) {
|
||||
var userDefault = sinon.stub(Users,"default",function() {
|
||||
return when.resolve("anon");
|
||||
});
|
||||
strategies.anonymousStrategy._success = strategies.anonymousStrategy.success;
|
||||
strategies.anonymousStrategy.success = function(user) {
|
||||
user.should.equal("anon");
|
||||
strategies.anonymousStrategy.success = strategies.anonymousStrategy._success;
|
||||
delete strategies.anonymousStrategy._success;
|
||||
done();
|
||||
};
|
||||
strategies.anonymousStrategy.authenticate({});
|
||||
});
|
||||
it('Fails if anon user not enabled',function(done) {
|
||||
var userDefault = sinon.stub(Users,"default",function() {
|
||||
return when.resolve(null);
|
||||
});
|
||||
strategies.anonymousStrategy._fail = strategies.anonymousStrategy.fail;
|
||||
strategies.anonymousStrategy.fail = function(err) {
|
||||
err.should.equal(401);
|
||||
strategies.anonymousStrategy.fail = strategies.anonymousStrategy._fail;
|
||||
delete strategies.anonymousStrategy._fail;
|
||||
done();
|
||||
};
|
||||
strategies.anonymousStrategy.authenticate({});
|
||||
});
|
||||
afterEach(function() {
|
||||
Users.default.restore();
|
||||
})
|
||||
});
|
||||
|
||||
describe("Bearer Strategy", function() {
|
||||
it('Rejects invalid token',function(done) {
|
||||
var getToken = sinon.stub(Tokens,"get",function(token) {
|
||||
return when.resolve(null);
|
||||
});
|
||||
|
||||
strategies.bearerStrategy("1234",function(err,user) {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
user.should.be.false();
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
} finally {
|
||||
getToken.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
it('Accepts valid token',function(done) {
|
||||
var getToken = sinon.stub(Tokens,"get",function(token) {
|
||||
return when.resolve({user:"user",scope:"scope"});
|
||||
});
|
||||
var getUser = sinon.stub(Users,"get",function(username) {
|
||||
return when.resolve("aUser");
|
||||
});
|
||||
|
||||
strategies.bearerStrategy("1234",function(err,user,opts) {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
user.should.equal("aUser");
|
||||
opts.should.have.a.property("scope","scope");
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
} finally {
|
||||
getToken.restore();
|
||||
getUser.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
it('Fail if no user for token',function(done) {
|
||||
var getToken = sinon.stub(Tokens,"get",function(token) {
|
||||
return when.resolve({user:"user",scope:"scope"});
|
||||
});
|
||||
var getUser = sinon.stub(Users,"get",function(username) {
|
||||
return when.resolve(null);
|
||||
});
|
||||
|
||||
strategies.bearerStrategy("1234",function(err,user,opts) {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
user.should.equal(false);
|
||||
should.not.exist(opts);
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
} finally {
|
||||
getToken.restore();
|
||||
getUser.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Client Password Strategy", function() {
|
||||
it('Accepts valid client',function(done) {
|
||||
var testClient = {id:"node-red-editor",secret:"not_available"};
|
||||
var getClient = sinon.stub(Clients,"get",function(client) {
|
||||
return when.resolve(testClient);
|
||||
});
|
||||
|
||||
strategies.clientPasswordStrategy(testClient.id,testClient.secret,function(err,client) {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
client.should.eql(testClient);
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
} finally {
|
||||
getClient.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
it('Rejects invalid client secret',function(done) {
|
||||
var testClient = {id:"node-red-editor",secret:"not_available"};
|
||||
var getClient = sinon.stub(Clients,"get",function(client) {
|
||||
return when.resolve(testClient);
|
||||
});
|
||||
|
||||
strategies.clientPasswordStrategy(testClient.id,"invalid_secret",function(err,client) {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
client.should.be.false();
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
} finally {
|
||||
getClient.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
it('Rejects invalid client id',function(done) {
|
||||
var getClient = sinon.stub(Clients,"get",function(client) {
|
||||
return when.resolve(null);
|
||||
});
|
||||
strategies.clientPasswordStrategy("invalid_id","invalid_secret",function(err,client) {
|
||||
try {
|
||||
should.not.exist(err);
|
||||
client.should.be.false();
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
} finally {
|
||||
getClient.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var userAuthentication;
|
||||
it('Blocks after 5 failures',function(done) {
|
||||
userAuthentication = sinon.stub(Users,"authenticate",function(username,password) {
|
||||
return when.resolve(null);
|
||||
});
|
||||
for (var z=0; z<5; z++) {
|
||||
strategies.passwordTokenExchange({},"user","badpassword","scope",function(err,token) {
|
||||
});
|
||||
}
|
||||
strategies.passwordTokenExchange({},"user","badpassword","scope",function(err,token) {
|
||||
try {
|
||||
err.toString().should.equal("Error: Too many login attempts. Wait 10 minutes and try again");
|
||||
token.should.be.false();
|
||||
done();
|
||||
} catch(e) {
|
||||
done(e);
|
||||
} finally {
|
||||
userAuthentication.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
181
test/unit/@node-red/editor-api/lib/auth/tokens_spec.js
Normal file
181
test/unit/@node-red/editor-api/lib/auth/tokens_spec.js
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* 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 when = require("when");
|
||||
var sinon = require("sinon");
|
||||
|
||||
var NR_TEST_UTILS = require("nr-test-utils");
|
||||
|
||||
var Tokens = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/tokens");
|
||||
|
||||
|
||||
describe("api/auth/tokens", function() {
|
||||
describe("#init",function() {
|
||||
it('loads sessions', function(done) {
|
||||
Tokens.init({}).then(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("#get",function() {
|
||||
it('returns a valid token', function(done) {
|
||||
Tokens.init({},{
|
||||
getSessions:function() {
|
||||
return when.resolve({"1234":{"user":"fred","expires":Date.now()+1000}});
|
||||
}
|
||||
}).then(function() {
|
||||
Tokens.get("1234").then(function(token) {
|
||||
try {
|
||||
token.should.have.a.property("user","fred");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null for an invalid token', function(done) {
|
||||
Tokens.init({},{
|
||||
getSessions:function() {
|
||||
return when.resolve({});
|
||||
}
|
||||
}).then(function() {
|
||||
Tokens.get("1234").then(function(token) {
|
||||
try {
|
||||
should.not.exist(token);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
it('returns null for an expired token', function(done) {
|
||||
var saveSessions = sinon.stub().returns(when.resolve());
|
||||
var expiryTime = Date.now()+50;
|
||||
Tokens.init({},{
|
||||
getSessions:function() {
|
||||
return when.resolve({"1234":{"user":"fred","expires":expiryTime}});
|
||||
},
|
||||
saveSessions: saveSessions
|
||||
}).then(function() {
|
||||
Tokens.get("1234").then(function(token) {
|
||||
try {
|
||||
should.exist(token);
|
||||
setTimeout(function() {
|
||||
Tokens.get("1234").then(function(token) {
|
||||
try {
|
||||
should.not.exist(token);
|
||||
saveSessions.calledOnce.should.be.true();
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
},100);
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a valid api token', function(done) {
|
||||
Tokens.init({
|
||||
tokens: [{
|
||||
token: "1234",
|
||||
user: "fred",
|
||||
}]
|
||||
},{
|
||||
getSessions:function() {
|
||||
return when.resolve({});
|
||||
}
|
||||
}).then(function() {
|
||||
Tokens.get("1234").then(function(token) {
|
||||
try {
|
||||
token.should.have.a.property("user","fred");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("#create",function() {
|
||||
it('creates a token', function(done) {
|
||||
var savedSession;
|
||||
Tokens.init({sessionExpiryTime: 10},{
|
||||
getSessions:function() {
|
||||
return when.resolve({});
|
||||
},
|
||||
saveSessions:function(sess) {
|
||||
savedSession = sess;
|
||||
return when.resolve();
|
||||
}
|
||||
});
|
||||
var expectedExpiryTime = Date.now()+10000;
|
||||
|
||||
|
||||
Tokens.create("user","client","scope").then(function(token) {
|
||||
try {
|
||||
should.exist(savedSession);
|
||||
var sessionKeys = Object.keys(savedSession);
|
||||
sessionKeys.should.have.lengthOf(1);
|
||||
|
||||
token.should.have.a.property('accessToken',sessionKeys[0]);
|
||||
savedSession[sessionKeys[0]].should.have.a.property('user','user');
|
||||
savedSession[sessionKeys[0]].should.have.a.property('client','client');
|
||||
savedSession[sessionKeys[0]].should.have.a.property('scope','scope');
|
||||
savedSession[sessionKeys[0]].should.have.a.property('expires');
|
||||
savedSession[sessionKeys[0]].expires.should.be.within(expectedExpiryTime-200,expectedExpiryTime+200);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#revoke", function() {
|
||||
it('revokes a token', function(done) {
|
||||
var savedSession;
|
||||
Tokens.init({},{
|
||||
getSessions:function() {
|
||||
return when.resolve({"1234":{"user":"fred","expires":Date.now()+1000}});
|
||||
},
|
||||
saveSessions:function(sess) {
|
||||
savedSession = sess;
|
||||
return when.resolve();
|
||||
}
|
||||
}).then(function() {
|
||||
Tokens.revoke("1234").then(function() {
|
||||
try {
|
||||
savedSession.should.not.have.a.property("1234");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
230
test/unit/@node-red/editor-api/lib/auth/users_spec.js
Normal file
230
test/unit/@node-red/editor-api/lib/auth/users_spec.js
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
var sinon = require('sinon');
|
||||
|
||||
var NR_TEST_UTILS = require("nr-test-utils");
|
||||
|
||||
var Users = NR_TEST_UTILS.require("@node-red/editor-api/lib/auth/users");
|
||||
|
||||
describe("api/auth/users", function() {
|
||||
after(function() {
|
||||
Users.init({});
|
||||
})
|
||||
describe('Initalised with a credentials object, no anon',function() {
|
||||
before(function() {
|
||||
Users.init({
|
||||
type:"credentials",
|
||||
users:{
|
||||
username:"fred",
|
||||
password:'$2a$08$LpYMefvGZ3MjAfZGzcoyR.1BcfHh4wy4NpbN.cEny5aHnWOqjKOXK',
|
||||
// 'password' -> require('bcryptjs').hashSync('password', 8);
|
||||
permissions:"*"
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('#get',function() {
|
||||
it('returns known user',function(done) {
|
||||
Users.get("fred").then(function(user) {
|
||||
try {
|
||||
user.should.have.a.property("username","fred");
|
||||
user.should.have.a.property("permissions","*");
|
||||
user.should.not.have.a.property("password");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null for unknown user', function(done) {
|
||||
Users.get("barney").then(function(user) {
|
||||
try {
|
||||
should.not.exist(user);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#default',function() {
|
||||
it('returns null for default user', function(done) {
|
||||
Users.default().then(function(user) {
|
||||
try {
|
||||
should.not.exist(user);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#authenticate',function() {
|
||||
it('authenticates a known user', function(done) {
|
||||
Users.authenticate('fred','password').then(function(user) {
|
||||
try {
|
||||
user.should.have.a.property("username","fred");
|
||||
user.should.have.a.property("permissions","*");
|
||||
user.should.not.have.a.property("password");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('rejects invalid password for a known user', function(done) {
|
||||
Users.authenticate('fred','wrong').then(function(user) {
|
||||
try {
|
||||
should.not.exist(user);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects invalid user', function(done) {
|
||||
Users.authenticate('barney','wrong').then(function(user) {
|
||||
try {
|
||||
should.not.exist(user);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Initalised with a credentials object including anon',function() {
|
||||
before(function() {
|
||||
Users.init({
|
||||
type:"credentials",
|
||||
users:[],
|
||||
default: { permissions: "*" }
|
||||
});
|
||||
});
|
||||
describe('#default',function() {
|
||||
it('returns default user', function(done) {
|
||||
Users.default().then(function(user) {
|
||||
try {
|
||||
user.should.have.a.property('anonymous',true);
|
||||
user.should.have.a.property('permissions','*');
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Initialised with a credentials object with user functions',function() {
|
||||
var authUsername = '';
|
||||
var authPassword = '';
|
||||
before(function() {
|
||||
Users.init({
|
||||
type:"credentials",
|
||||
users:function(username) {
|
||||
return when.resolve({'username':'dave','permissions':'read'});
|
||||
},
|
||||
authenticate: function(username,password) {
|
||||
authUsername = username;
|
||||
authPassword = password;
|
||||
return when.resolve({'username':'pete','permissions':'write'});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('#get',function() {
|
||||
it('delegates get user',function(done) {
|
||||
Users.get('dave').then(function(user) {
|
||||
try {
|
||||
user.should.have.a.property("username","dave");
|
||||
user.should.have.a.property("permissions","read");
|
||||
user.should.not.have.a.property("password");
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('delegates authenticate user',function(done) {
|
||||
Users.authenticate('pete','secret').then(function(user) {
|
||||
try {
|
||||
user.should.have.a.property("username","pete");
|
||||
user.should.have.a.property("permissions","write");
|
||||
user.should.not.have.a.property("password");
|
||||
authUsername.should.equal('pete');
|
||||
authPassword.should.equal('secret');
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Initialised with bad settings to test else cases',function() {
|
||||
before(function() {
|
||||
Users.init({
|
||||
type:"foo",
|
||||
users:{
|
||||
username:"fred",
|
||||
password:'$2a$08$LpYMefvGZ3MjAfZGzcoyR.1BcfHh4wy4NpbN.cEny5aHnWOqjKOXK',
|
||||
permissions:"*"
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('#get',function() {
|
||||
it('should fail to return user fred',function(done) {
|
||||
Users.get("fred").then(function(userf) {
|
||||
try {
|
||||
should.not.exist(userf);
|
||||
done();
|
||||
} catch(err) {
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Initialised with default set as function',function() {
|
||||
before(function() {
|
||||
Users.init({
|
||||
type:"credentials",
|
||||
default: function() { return("Done"); }
|
||||
});
|
||||
});
|
||||
after(function() {
|
||||
Users.init({});
|
||||
});
|
||||
describe('#default',function() {
|
||||
it('handles api.default being a function',function(done) {
|
||||
Users.should.have.property('default').which.is.a.Function();
|
||||
(Users.default()).should.equal("Done");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user