mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge pull request #558 from knolleary/auth
Add bearer token authentication to Editor
This commit is contained in:
commit
3123aa6279
@ -477,22 +477,24 @@
|
|||||||
var label = (this.name||this.payload).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
var label = (this.name||this.payload).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||||
if (this.payloadType === "date") { label = "timestamp"; }
|
if (this.payloadType === "date") { label = "timestamp"; }
|
||||||
if (this.payloadType === "none") { label = "blank"; }
|
if (this.payloadType === "none") { label = "blank"; }
|
||||||
d3.xhr("inject/"+this.id).post(function(err,resp) {
|
|
||||||
if (err) {
|
$.ajax({
|
||||||
if (err.status == 404) {
|
url: "inject/"+this.id,
|
||||||
RED.notify("<strong>Error</strong>: inject node not deployed","error");
|
type:"POST",
|
||||||
} else if (err.status == 500) {
|
success: function(resp) {
|
||||||
RED.notify("<strong>Error</strong>: inject failed, see log for details.","error");
|
RED.notify("Successfully injected: "+label,"success");
|
||||||
} else if (err.status == 0) {
|
},
|
||||||
RED.notify("<strong>Error</strong>: no response from server","error");
|
error: function(jqXHR,textStatus,errorThrown) {
|
||||||
} else {
|
if (jqXHR.status == 404) {
|
||||||
RED.notify("<strong>Error</strong>: unexpected error: ("+err.status+")"+err.response,"error");
|
RED.notify("<strong>Error</strong>: inject node not deployed","error");
|
||||||
}
|
} else if (jqXHR.status == 500) {
|
||||||
} else if (resp.status == 200) {
|
RED.notify("<strong>Error</strong>: inject failed, see log for details.","error");
|
||||||
RED.notify("Successfully injected: "+label,"success");
|
} else if (jqXHR.status == 0) {
|
||||||
|
RED.notify("<strong>Error</strong>: no response from server","error");
|
||||||
} else {
|
} else {
|
||||||
RED.notify("<strong>Error</strong>: unexpected response: ("+resp.status+") "+resp.response,"error");
|
RED.notify("<strong>Error</strong>: unexpected error: ("+jqXHR.status+")"+textStatus,"error");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,19 +80,19 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RED.httpAdmin.post("/inject/:id", function(req,res) {
|
RED.httpAdmin.post("/inject/:id", RED.auth.needsPermission("inject.write"), function(req,res) {
|
||||||
var node = RED.nodes.getNode(req.params.id);
|
var node = RED.nodes.getNode(req.params.id);
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
try {
|
try {
|
||||||
node.receive();
|
node.receive();
|
||||||
res.send(200);
|
res.send(200);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
res.send(500);
|
res.send(500);
|
||||||
node.error("Inject failed:"+err);
|
node.error("Inject failed:"+err);
|
||||||
console.log(err.stack);
|
console.log(err.stack);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.send(404);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
res.send(404);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -119,7 +119,7 @@ module.exports = function(RED) {
|
|||||||
});
|
});
|
||||||
RED.log.addHandler(DebugNode.logHandler);
|
RED.log.addHandler(DebugNode.logHandler);
|
||||||
|
|
||||||
RED.httpAdmin.post("/debug/:id/:state", function(req,res) {
|
RED.httpAdmin.post("/debug/:id/:state", RED.auth.needsPermission("debug.write"), function(req,res) {
|
||||||
var node = RED.nodes.getNode(req.params.id);
|
var node = RED.nodes.getNode(req.params.id);
|
||||||
var state = req.params.state;
|
var state = req.params.state;
|
||||||
if (node !== null && typeof node !== "undefined" ) {
|
if (node !== null && typeof node !== "undefined" ) {
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "3.17.2",
|
"express": "3.17.2",
|
||||||
"when": "3.4.6",
|
"when": "3.4.6",
|
||||||
|
"bcryptjs": "2.1.0",
|
||||||
"nopt": "3.0.1",
|
"nopt": "3.0.1",
|
||||||
"mqtt": "0.3.x",
|
"mqtt": "0.3.x",
|
||||||
"ws": "0.4.32",
|
"ws": "0.4.32",
|
||||||
@ -49,7 +50,11 @@
|
|||||||
"is-utf8":"0.2.0",
|
"is-utf8":"0.2.0",
|
||||||
"serialport":"1.4.10",
|
"serialport":"1.4.10",
|
||||||
"feedparser":"0.19.2",
|
"feedparser":"0.19.2",
|
||||||
"fs.notify":"0.0.4"
|
"fs.notify":"0.0.4",
|
||||||
|
"passport":"0.2.1",
|
||||||
|
"passport-http-bearer":"1.0.1",
|
||||||
|
"passport-oauth2-client-password":"0.1.2",
|
||||||
|
"oauth2orize":"1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"grunt": "0.4.5",
|
"grunt": "0.4.5",
|
||||||
|
@ -31,17 +31,18 @@
|
|||||||
<body spellcheck="false">
|
<body spellcheck="false">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<span class="logo"><img src="node-red.png"> <span>Node-RED</span></span>
|
<span class="logo"><img src="node-red.png"> <span>Node-RED</span></span>
|
||||||
<ul class="header-toolbar">
|
<ul class="header-toolbar hide">
|
||||||
<li><span class="deploy-button-group button-group">
|
<li><span class="deploy-button-group button-group">
|
||||||
<a id="btn-deploy" class="action-deploy disabled" href="#"><img id="btn-icn-deploy" src="images/deploy-full-o.png"> <span>Deploy</span></a>
|
<a id="btn-deploy" class="action-deploy disabled" href="#"><img id="btn-icn-deploy" src="images/deploy-full-o.png"> <span>Deploy</span></a>
|
||||||
<a id="btn-deploy-options" data-toggle="dropdown" class="" href="#"><i class="fa fa-caret-down"></i></a>
|
<a id="btn-deploy-options" data-toggle="dropdown" class="" href="#"><i class="fa fa-caret-down"></i></a>
|
||||||
</span></li>
|
</span></li>
|
||||||
|
<li><a id="btn-usermenu" class="button hide" data-toggle="dropdown" href="#"><i class="fa fa-user"></i></a></li>
|
||||||
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
|
<li><a id="btn-sidemenu" class="button" data-toggle="dropdown" href="#"><i class="fa fa-bars"></i></a></li>
|
||||||
<ul>
|
<ul>
|
||||||
</div>
|
</div>
|
||||||
<div id="main-container" class="sidebar-closed">
|
<div id="main-container" class="sidebar-closed hide">
|
||||||
<div id="palette">
|
<div id="palette">
|
||||||
<img src="spin.svg" class="palette-spinner"/>
|
<img src="spin.svg" class="palette-spinner hide"/>
|
||||||
<div id="palette-container" class="palette-scroll">
|
<div id="palette-container" class="palette-scroll">
|
||||||
</div>
|
</div>
|
||||||
<div id="palette-search">
|
<div id="palette-search">
|
||||||
@ -80,6 +81,7 @@
|
|||||||
|
|
||||||
<div id="notifications"></div>
|
<div id="notifications"></div>
|
||||||
<div id="dropTarget"><div>Drop the flow here<br/><i class="fa fa-download"></i></div></div>
|
<div id="dropTarget"><div>Drop the flow here<br/><i class="fa fa-download"></i></div></div>
|
||||||
|
<div id="shade"></div>
|
||||||
|
|
||||||
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
|
<div id="dialog" class="hide"><form id="dialog-form" class="form-horizontal"></form></div>
|
||||||
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div>
|
<div id="node-config-dialog" class="hide"><form id="dialog-config-form" class="form-horizontal"></form><div class="form-tips" id="node-config-dialog-user-count"></div></div>
|
||||||
@ -246,12 +248,13 @@
|
|||||||
<script src="orion/built-editor.min.js"></script>
|
<script src="orion/built-editor.min.js"></script>
|
||||||
<script src="d3.v3.min.js"></script>
|
<script src="d3.v3.min.js"></script>
|
||||||
<script src="red/main.js"></script>
|
<script src="red/main.js"></script>
|
||||||
|
<script src="red/settings.js"></script>
|
||||||
|
<script src="red/user.js"></script>
|
||||||
<script src="red/comms.js"></script>
|
<script src="red/comms.js"></script>
|
||||||
<script src="red/ui/state.js"></script>
|
<script src="red/ui/state.js"></script>
|
||||||
<script src="red/nodes.js"></script>
|
<script src="red/nodes.js"></script>
|
||||||
<script src="red/history.js"></script>
|
<script src="red/history.js"></script>
|
||||||
<script src="red/validators.js"></script>
|
<script src="red/validators.js"></script>
|
||||||
<script src="red/settings.js"></script>
|
|
||||||
<script src="red/ui/menu.js"></script>
|
<script src="red/ui/menu.js"></script>
|
||||||
<script src="red/ui/keyboard.js"></script>
|
<script src="red/ui/keyboard.js"></script>
|
||||||
<script src="red/ui/tabs.js"></script>
|
<script src="red/ui/tabs.js"></script>
|
||||||
|
BIN
public/node-red-256.png
Normal file
BIN
public/node-red-256.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
@ -17,27 +17,47 @@
|
|||||||
RED.comms = (function() {
|
RED.comms = (function() {
|
||||||
|
|
||||||
var errornotification = null;
|
var errornotification = null;
|
||||||
|
var clearErrorTimer = null;
|
||||||
|
|
||||||
var subscriptions = {};
|
var subscriptions = {};
|
||||||
var ws;
|
var ws;
|
||||||
|
var pendingAuth = false;
|
||||||
|
|
||||||
function connectWS() {
|
function connectWS() {
|
||||||
var path = location.hostname+":"+location.port+document.location.pathname;
|
var path = location.hostname+":"+location.port+document.location.pathname;
|
||||||
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
|
path = path+(path.slice(-1) == "/"?"":"/")+"comms";
|
||||||
path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
|
path = "ws"+(document.location.protocol=="https:"?"s":"")+"://"+path;
|
||||||
ws = new WebSocket(path);
|
var auth_tokens = RED.settings.get("auth-tokens");
|
||||||
ws.onopen = function() {
|
pendingAuth = (auth_tokens!=null);
|
||||||
if (errornotification) {
|
|
||||||
errornotification.close();
|
function completeConnection() {
|
||||||
errornotification = null;
|
|
||||||
}
|
|
||||||
for (var t in subscriptions) {
|
for (var t in subscriptions) {
|
||||||
if (subscriptions.hasOwnProperty(t)) {
|
if (subscriptions.hasOwnProperty(t)) {
|
||||||
ws.send(JSON.stringify({subscribe:t}));
|
ws.send(JSON.stringify({subscribe:t}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ws = new WebSocket(path);
|
||||||
|
ws.onopen = function() {
|
||||||
|
if (errornotification) {
|
||||||
|
clearErrorTimer = setTimeout(function() {
|
||||||
|
errornotification.close();
|
||||||
|
errornotification = null;
|
||||||
|
},1000);
|
||||||
|
}
|
||||||
|
if (pendingAuth) {
|
||||||
|
ws.send(JSON.stringify({auth:auth_tokens.access_token}));
|
||||||
|
} else {
|
||||||
|
completeConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
ws.onmessage = function(event) {
|
ws.onmessage = function(event) {
|
||||||
var msg = JSON.parse(event.data);
|
var msg = JSON.parse(event.data);
|
||||||
if (msg.topic) {
|
if (pendingAuth && msg.auth == "ok") {
|
||||||
|
pendingAuth = false;
|
||||||
|
completeConnection();
|
||||||
|
} else if (msg.topic) {
|
||||||
for (var t in subscriptions) {
|
for (var t in subscriptions) {
|
||||||
if (subscriptions.hasOwnProperty(t)) {
|
if (subscriptions.hasOwnProperty(t)) {
|
||||||
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
|
||||||
@ -56,6 +76,9 @@ RED.comms = (function() {
|
|||||||
ws.onclose = function() {
|
ws.onclose = function() {
|
||||||
if (errornotification == null) {
|
if (errornotification == null) {
|
||||||
errornotification = RED.notify("<b>Error</b>: Lost connection to server","error",true);
|
errornotification = RED.notify("<b>Error</b>: Lost connection to server","error",true);
|
||||||
|
} else if (clearErrorTimer) {
|
||||||
|
clearTimeout(clearErrorTimer);
|
||||||
|
clearErrorTimer = null;
|
||||||
}
|
}
|
||||||
setTimeout(connectWS,1000);
|
setTimeout(connectWS,1000);
|
||||||
}
|
}
|
||||||
|
@ -22,34 +22,6 @@ var RED = (function() {
|
|||||||
}
|
}
|
||||||
var deploymentType = "full";
|
var deploymentType = "full";
|
||||||
|
|
||||||
|
|
||||||
function hideDropTarget() {
|
|
||||||
$("#dropTarget").hide();
|
|
||||||
RED.keyboard.remove(/* ESCAPE */ 27);
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#chart').on("dragenter",function(event) {
|
|
||||||
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
|
||||||
$("#dropTarget").css({display:'table'});
|
|
||||||
RED.keyboard.add(/* ESCAPE */ 27,hideDropTarget);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#dropTarget').on("dragover",function(event) {
|
|
||||||
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on("dragleave",function(event) {
|
|
||||||
hideDropTarget();
|
|
||||||
})
|
|
||||||
.on("drop",function(event) {
|
|
||||||
var data = event.originalEvent.dataTransfer.getData("text/plain");
|
|
||||||
hideDropTarget();
|
|
||||||
RED.view.importNodes(data);
|
|
||||||
event.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
function save(force) {
|
function save(force) {
|
||||||
if (RED.view.dirty()) {
|
if (RED.view.dirty()) {
|
||||||
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
|
//$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy
|
||||||
@ -291,13 +263,12 @@ var RED = (function() {
|
|||||||
dialog.modal();
|
dialog.modal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function changeDeploymentType(type) {
|
function changeDeploymentType(type) {
|
||||||
deploymentType = type;
|
deploymentType = type;
|
||||||
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
|
$("#btn-deploy img").attr("src",deploymentTypes[type].img);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function() {
|
function loadEditor() {
|
||||||
RED.menu.init({id:"btn-sidemenu",
|
RED.menu.init({id:"btn-sidemenu",
|
||||||
options: [
|
options: [
|
||||||
{id:"btn-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
|
{id:"btn-sidebar",label:"Sidebar",toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true},
|
||||||
@ -339,23 +310,68 @@ var RED = (function() {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
RED.menu.init({id:"workspace-subflow-edit-menu",
|
if (RED.settings.user) {
|
||||||
options: [
|
RED.menu.init({id:"btn-usermenu",
|
||||||
{id:"btn-subflow-add-input",label:"Add Input", onselect:function() { }},
|
options: []
|
||||||
{id:"btn-subflow-add-output",label:"Add Output", onselect:function() { }},
|
});
|
||||||
{id:"btn-subflow-edit-name",label:"Edit Name", onselect:function() { }},
|
|
||||||
{id:"btn-subflow-delete",label:"Delete", onselect:function() { }},
|
var updateUserMenu = function() {
|
||||||
]
|
$("#btn-usermenu-submenu li").remove();
|
||||||
});
|
if (RED.settings.user.anonymous) {
|
||||||
|
RED.menu.addItem("btn-usermenu",{
|
||||||
|
id:"btn-login",
|
||||||
|
label:"Login",
|
||||||
|
onselect: function() {
|
||||||
|
RED.user.login({cancelable:true},function() {
|
||||||
|
RED.settings.load(function() {
|
||||||
|
RED.notify("Logged in as "+RED.settings.user.username,"success");
|
||||||
|
updateUserMenu();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
RED.menu.addItem("btn-usermenu",{
|
||||||
|
id:"btn-username",
|
||||||
|
label:"<b>"+RED.settings.user.username+"</b>"
|
||||||
|
});
|
||||||
|
RED.menu.addItem("btn-usermenu",{
|
||||||
|
id:"btn-logout",
|
||||||
|
label:"Logout",
|
||||||
|
onselect: function() {
|
||||||
|
RED.user.logout();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
updateUserMenu();
|
||||||
|
} else {
|
||||||
|
$("#btn-usermenu").parent().hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#main-container").show();
|
||||||
|
$(".header-toolbar").show();
|
||||||
|
|
||||||
|
RED.library.init();
|
||||||
|
RED.palette.init();
|
||||||
|
RED.sidebar.init();
|
||||||
|
RED.view.init();
|
||||||
|
|
||||||
RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();});
|
RED.keyboard.add(/* ? */ 191,{shift:true},function(){showHelp();d3.event.preventDefault();});
|
||||||
loadSettings();
|
|
||||||
RED.comms.connect();
|
RED.comms.connect();
|
||||||
|
loadNodeList();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
|
||||||
|
document.title = "Node-RED : "+window.location.hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
RED.settings.init(loadEditor);
|
||||||
});
|
});
|
||||||
|
|
||||||
if ((window.location.hostname !== "localhost") && (window.location.hostname !== "127.0.0.1")) {
|
|
||||||
document.title = "Node-RED : "+window.location.hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2014 Antoine Aflalo
|
* Copyright 2014 IBM, Antoine Aflalo
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
|
|
||||||
RED.settings = (function () {
|
RED.settings = (function () {
|
||||||
|
|
||||||
|
var loadedSettings = {};
|
||||||
|
|
||||||
var hasLocalStorage = function () {
|
var hasLocalStorage = function () {
|
||||||
try {
|
try {
|
||||||
return 'localStorage' in window && window['localStorage'] !== null;
|
return 'localStorage' in window && window['localStorage'] !== null;
|
||||||
@ -51,14 +54,37 @@ RED.settings = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var setProperties = function(data) {
|
var setProperties = function(data) {
|
||||||
for(var prop in data) {
|
for (var prop in loadedSettings) {
|
||||||
if(data.hasOwnProperty(prop)) {
|
if (loadedSettings.hasOwnProperty(prop) && RED.settings.hasOwnProperty(prop)) {
|
||||||
|
delete RED.settings[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (prop in data) {
|
||||||
|
if (data.hasOwnProperty(prop)) {
|
||||||
RED.settings[prop] = data[prop];
|
RED.settings[prop] = data[prop];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
loadedSettings = data;
|
||||||
};
|
};
|
||||||
|
|
||||||
var init = function (callback) {
|
var init = function (done) {
|
||||||
|
$.ajaxSetup({
|
||||||
|
beforeSend: function(jqXHR,settings) {
|
||||||
|
// Only attach auth header for requests to relative paths
|
||||||
|
if (!/^\s*(https?:|\/|\.)/.test(settings.url)) {
|
||||||
|
var auth_tokens = RED.settings.get("auth-tokens");
|
||||||
|
if (auth_tokens) {
|
||||||
|
jqXHR.setRequestHeader("authorization","bearer "+auth_tokens.access_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
load(done);
|
||||||
|
}
|
||||||
|
|
||||||
|
var load = function(done) {
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "application/json"
|
"Accept": "application/json"
|
||||||
@ -68,18 +94,29 @@ RED.settings = (function () {
|
|||||||
url: 'settings',
|
url: 'settings',
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
setProperties(data);
|
setProperties(data);
|
||||||
|
if (RED.settings.user && RED.settings.user.anonymous) {
|
||||||
|
RED.settings.remove("auth-tokens");
|
||||||
|
}
|
||||||
console.log("Node-RED: " + data.version);
|
console.log("Node-RED: " + data.version);
|
||||||
callback();
|
done();
|
||||||
|
},
|
||||||
|
error: function(jqXHR,textStatus,errorThrown) {
|
||||||
|
if (jqXHR.status === 401) {
|
||||||
|
RED.user.login(function() { load(done); });
|
||||||
|
} else {
|
||||||
|
console.log("Unexpected error:",jqXHR.status,textStatus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
init: init,
|
||||||
|
load: load,
|
||||||
set: set,
|
set: set,
|
||||||
get: get,
|
get: get,
|
||||||
remove: remove,
|
remove: remove
|
||||||
init : init
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
();
|
();
|
@ -66,9 +66,6 @@ RED.library = (function() {
|
|||||||
$("#btn-import-library-submenu").replaceWith(menu);
|
$("#btn-import-library-submenu").replaceWith(menu);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
loadFlowLibrary();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function createUI(options) {
|
function createUI(options) {
|
||||||
var libraryData = {};
|
var libraryData = {};
|
||||||
@ -360,6 +357,9 @@ RED.library = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
init: function() {
|
||||||
|
loadFlowLibrary();
|
||||||
|
},
|
||||||
create: createUI,
|
create: createUI,
|
||||||
loadFlowLibrary: loadFlowLibrary
|
loadFlowLibrary: loadFlowLibrary
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,12 @@ RED.menu = (function() {
|
|||||||
|
|
||||||
var button = $("#"+options.id);
|
var button = $("#"+options.id);
|
||||||
|
|
||||||
|
//button.click(function(event) {
|
||||||
|
// $("#"+options.id+"-submenu").show();
|
||||||
|
// event.preventDefault();
|
||||||
|
//});
|
||||||
|
|
||||||
|
|
||||||
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}).insertAfter(button);
|
var topMenu = $("<ul/>",{id:options.id+"-submenu", class:"dropdown-menu pull-right"}).insertAfter(button);
|
||||||
|
|
||||||
for (var i=0;i<options.options.length;i++) {
|
for (var i=0;i<options.options.length;i++) {
|
||||||
|
@ -36,8 +36,6 @@ RED.palette = (function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
core.forEach(createCategoryContainer);
|
|
||||||
|
|
||||||
function setLabel(type, el,label) {
|
function setLabel(type, el,label) {
|
||||||
var nodeWidth = 80;
|
var nodeWidth = 80;
|
||||||
var nodeHeight = 25;
|
var nodeHeight = 25;
|
||||||
@ -241,32 +239,37 @@ RED.palette = (function() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#palette-search-input").focus(function(e) {
|
function init() {
|
||||||
RED.keyboard.disable();
|
$(".palette-spinner").show();
|
||||||
});
|
core.forEach(createCategoryContainer);
|
||||||
$("#palette-search-input").blur(function(e) {
|
$("#palette-search-input").focus(function(e) {
|
||||||
RED.keyboard.enable();
|
RED.keyboard.disable();
|
||||||
});
|
|
||||||
|
|
||||||
$("#palette-search-clear").on("click",function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
$("#palette-search-input").val("");
|
|
||||||
filterChange();
|
|
||||||
$("#palette-search-input").focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#palette-search-input").val("");
|
|
||||||
$("#palette-search-input").on("keyup",function() {
|
|
||||||
filterChange();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#palette-search-input").on("focus",function() {
|
|
||||||
$("body").one("mousedown",function() {
|
|
||||||
$("#palette-search-input").blur();
|
|
||||||
});
|
});
|
||||||
});
|
$("#palette-search-input").blur(function(e) {
|
||||||
|
RED.keyboard.enable();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#palette-search-clear").on("click",function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$("#palette-search-input").val("");
|
||||||
|
filterChange();
|
||||||
|
$("#palette-search-input").focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#palette-search-input").val("");
|
||||||
|
$("#palette-search-input").on("keyup",function() {
|
||||||
|
filterChange();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#palette-search-input").on("focus",function() {
|
||||||
|
$("body").one("mousedown",function() {
|
||||||
|
$("#palette-search-input").blur();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
init: init,
|
||||||
add:addNodeType,
|
add:addNodeType,
|
||||||
remove:removeNodeType,
|
remove:removeNodeType,
|
||||||
hide:hideNodeType,
|
hide:hideNodeType,
|
||||||
|
@ -26,6 +26,7 @@ RED.sidebar = (function() {
|
|||||||
$("#"+tab.id).remove();
|
$("#"+tab.id).remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function addTab(title,content,closeable) {
|
function addTab(title,content,closeable) {
|
||||||
$("#sidebar-content").append(content);
|
$("#sidebar-content").append(content);
|
||||||
$(content).hide();
|
$(content).hide();
|
||||||
@ -129,22 +130,23 @@ RED.sidebar = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showSidebar(id) {
|
function showSidebar(id) {
|
||||||
//RED.menu.setSelected("btn-sidebar", true);
|
if (id) {
|
||||||
sidebar_tabs.activateTab("tab-" + id);
|
sidebar_tabs.activateTab("tab-"+id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function containsTab(id) {
|
function containsTab(id) {
|
||||||
return sidebar_tabs.contains("tab-"+id);
|
return sidebar_tabs.contains("tab-"+id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function init () {
|
||||||
$(function() {
|
|
||||||
RED.keyboard.add(/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("btn-sidebar",!RED.menu.isSelected("btn-sidebar"));d3.event.preventDefault();});
|
RED.keyboard.add(/* SPACE */ 32,{ctrl:true},function(){RED.menu.setSelected("btn-sidebar",!RED.menu.isSelected("btn-sidebar"));d3.event.preventDefault();});
|
||||||
showSidebar("info");
|
showSidebar();
|
||||||
});
|
RED.sidebar.info.show();
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
init: init,
|
||||||
addTab: addTab,
|
addTab: addTab,
|
||||||
removeTab: removeTab,
|
removeTab: removeTab,
|
||||||
show: showSidebar,
|
show: showSidebar,
|
||||||
|
@ -32,7 +32,12 @@ RED.sidebar.info = (function() {
|
|||||||
content.style.paddingLeft = "4px";
|
content.style.paddingLeft = "4px";
|
||||||
content.style.paddingRight = "4px";
|
content.style.paddingRight = "4px";
|
||||||
|
|
||||||
RED.sidebar.addTab("info",content);
|
function show() {
|
||||||
|
if (!RED.sidebar.containsTab("info")) {
|
||||||
|
RED.sidebar.addTab("info",content,false);
|
||||||
|
}
|
||||||
|
RED.sidebar.show("info");
|
||||||
|
}
|
||||||
|
|
||||||
function jsonFilter(key,value) {
|
function jsonFilter(key,value) {
|
||||||
if (key === "") {
|
if (key === "") {
|
||||||
@ -116,6 +121,7 @@ RED.sidebar.info = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
show: show,
|
||||||
refresh:refresh,
|
refresh:refresh,
|
||||||
clear: function() {
|
clear: function() {
|
||||||
$("#tab-info").html("");
|
$("#tab-info").html("");
|
||||||
|
@ -365,7 +365,8 @@ RED.view = (function() {
|
|||||||
RED.history.push({t:'add',workspaces:[ws],dirty:dirty});
|
RED.history.push({t:'add',workspaces:[ws],dirty:dirty});
|
||||||
RED.view.dirty(true);
|
RED.view.dirty(true);
|
||||||
}
|
}
|
||||||
$(function() {
|
|
||||||
|
function init() {
|
||||||
$('#btn-workspace-add-tab').on("click",addWorkspace);
|
$('#btn-workspace-add-tab').on("click",addWorkspace);
|
||||||
|
|
||||||
RED.menu.setAction('btn-workspace-add',addWorkspace);
|
RED.menu.setAction('btn-workspace-add',addWorkspace);
|
||||||
@ -375,7 +376,7 @@ RED.view = (function() {
|
|||||||
RED.menu.setAction('btn-workspace-delete',function() {
|
RED.menu.setAction('btn-workspace-delete',function() {
|
||||||
deleteWorkspace(activeWorkspace);
|
deleteWorkspace(activeWorkspace);
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
function deleteWorkspace(id) {
|
function deleteWorkspace(id) {
|
||||||
if (workspace_tabs.count() == 1) {
|
if (workspace_tabs.count() == 1) {
|
||||||
@ -2052,7 +2053,35 @@ RED.view = (function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function hideDropTarget() {
|
||||||
|
$("#dropTarget").hide();
|
||||||
|
RED.keyboard.remove(/* ESCAPE */ 27);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#chart').on("dragenter",function(event) {
|
||||||
|
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
||||||
|
$("#dropTarget").css({display:'table'});
|
||||||
|
RED.keyboard.add(/* ESCAPE */ 27,hideDropTarget);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#dropTarget').on("dragover",function(event) {
|
||||||
|
if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("dragleave",function(event) {
|
||||||
|
hideDropTarget();
|
||||||
|
})
|
||||||
|
.on("drop",function(event) {
|
||||||
|
var data = event.originalEvent.dataTransfer.getData("text/plain");
|
||||||
|
hideDropTarget();
|
||||||
|
RED.view.importNodes(data);
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
init: init,
|
||||||
state:function(state) {
|
state:function(state) {
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
return mouse_mode
|
return mouse_mode
|
||||||
|
117
public/red/user.js
Normal file
117
public/red/user.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
RED.user = (function() {
|
||||||
|
|
||||||
|
function login(opts,done) {
|
||||||
|
if (typeof opts == 'function') {
|
||||||
|
done = opts;
|
||||||
|
opts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialog = $('<div id="node-dialog-login" class="hide">'+
|
||||||
|
'<div style="display: inline-block;width: 250px; vertical-align: top; margin-right: 10px; margin-bottom: 20px;"><img src="node-red-256.png"/></div>'+
|
||||||
|
'<div style="display: inline-block; width: 250px; vertical-align: bottom; margin-left: 10px; margin-bottom: 20px;">'+
|
||||||
|
'<form id="node-dialog-login-fields" class="form-horizontal" style="margin-bottom: 0px;"></form>'+
|
||||||
|
'</div>'+
|
||||||
|
'</div>');
|
||||||
|
|
||||||
|
dialog.dialog({
|
||||||
|
autoOpen: false,
|
||||||
|
dialogClass: "ui-dialog-no-close",
|
||||||
|
modal: true,
|
||||||
|
closeOnEscape: false,
|
||||||
|
width: 600,
|
||||||
|
resizable: false,
|
||||||
|
draggable: false
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#node-dialog-login-fields").empty();
|
||||||
|
$.ajax({
|
||||||
|
dataType: "json",
|
||||||
|
url: "auth/login",
|
||||||
|
success: function(data) {
|
||||||
|
if (data.type == "credentials") {
|
||||||
|
var i=0;
|
||||||
|
for (;i<data.prompts.length;i++) {
|
||||||
|
var field = data.prompts[i];
|
||||||
|
var row = $("<div/>",{class:"form-row"});
|
||||||
|
$('<label for="node-dialog-login-'+field.id+'">'+field.label+':</label><br/>').appendTo(row);
|
||||||
|
$('<input style="width: 100%" id="node-dialog-login-'+field.id+'" type="'+field.type+'" tabIndex="'+(i+1)+'"/>').appendTo(row);
|
||||||
|
row.appendTo("#node-dialog-login-fields");
|
||||||
|
}
|
||||||
|
$('<div class="form-row" style="text-align: right; margin-top: 10px;"><span id="node-dialog-login-failed" style="line-height: 2em;float:left;" class="hide">Login failed</span><img src="spin.svg" style="height: 30px; margin-right: 10px; " class="login-spinner hide"/>'+
|
||||||
|
(opts.cancelable?'<a href="#" id="node-dialog-login-cancel" style="margin-right: 20px;" tabIndex="'+(i+1)+'">Cancel</a>':'')+
|
||||||
|
'<a href="#" id="node-dialog-login-submit" tabIndex="'+(i+2)+'">Login</a></div>').appendTo("#node-dialog-login-fields");
|
||||||
|
$("#node-dialog-login-submit").button().click(function( event ) {
|
||||||
|
$("#node-dialog-login-submit").button("option","disabled",true);
|
||||||
|
$("#node-dialog-login-failed").hide();
|
||||||
|
$(".login-spinner").show();
|
||||||
|
|
||||||
|
var body = {
|
||||||
|
client_id: "node-red-editor",
|
||||||
|
grant_type: "password",
|
||||||
|
scope:"*"
|
||||||
|
}
|
||||||
|
for (var i=0;i<data.prompts.length;i++) {
|
||||||
|
var field = data.prompts[i];
|
||||||
|
body[field.id] = $("#node-dialog-login-"+field.id).val();
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url:"auth/token",
|
||||||
|
type: "POST",
|
||||||
|
data: body
|
||||||
|
}).done(function(data,textStatus,xhr) {
|
||||||
|
RED.settings.set("auth-tokens",data);
|
||||||
|
$("#node-dialog-login").dialog('destroy').remove();
|
||||||
|
done();
|
||||||
|
}).fail(function(jqXHR,textStatus,errorThrown) {
|
||||||
|
RED.settings.remove("auth-tokens");
|
||||||
|
$("#node-dialog-login-failed").show();
|
||||||
|
}).always(function() {
|
||||||
|
$("#node-dialog-login-submit").button("option","disabled",false);
|
||||||
|
$(".login-spinner").hide();
|
||||||
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
||||||
|
if (opts.cancelable) {
|
||||||
|
$("#node-dialog-login-cancel").button().click(function( event ) {
|
||||||
|
$("#node-dialog-login").dialog('destroy').remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.dialog("open");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
$.ajax({
|
||||||
|
url: "auth/revoke",
|
||||||
|
type: "POST",
|
||||||
|
data: {token:RED.settings.get("auth-tokens").access_token},
|
||||||
|
success: function() {
|
||||||
|
RED.settings.remove("auth-tokens");
|
||||||
|
document.location.reload(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
login: login,
|
||||||
|
logout: logout
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
@ -29,8 +29,11 @@ body {
|
|||||||
background: #000;
|
background: #000;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0px 0px 0px 20px;
|
padding: 0px 0px 0px 20px;
|
||||||
|
color: #C7C7C7;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#dropTarget {
|
#dropTarget {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0; bottom: 0;
|
top: 0; bottom: 0;
|
||||||
@ -66,7 +69,6 @@ span.logo {
|
|||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #C7C7C7;
|
|
||||||
}
|
}
|
||||||
span.logo span {
|
span.logo span {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
@ -83,12 +85,15 @@ span.logo img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#header ul.header-toolbar > li {
|
#header ul.header-toolbar > li {
|
||||||
display: inline-block;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#header ul.header-toolbar > li {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-khtml-user-select: none;
|
-khtml-user-select: none;
|
||||||
@ -179,7 +184,7 @@ span.deploy-button-group.open > #btn-deploy.disabled + a {
|
|||||||
|
|
||||||
.button-group {
|
.button-group {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: auto 10px;
|
margin: auto 15px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
background: #555;
|
background: #555;
|
||||||
clear: both;
|
clear: both;
|
||||||
@ -197,16 +202,16 @@ span.deploy-button-group.open > #btn-deploy.disabled + a {
|
|||||||
.button-group > a:last-child {
|
.button-group > a:last-child {
|
||||||
}
|
}
|
||||||
|
|
||||||
#btn-sidemenu {
|
#header .button {
|
||||||
font-size: 20px !important;
|
font-size: 20px !important;
|
||||||
}
|
}
|
||||||
#btn-sidemenu:active, #btn-sidemenu.active {
|
#header .button:active, #header .button.active {
|
||||||
background: #121212;
|
background: #121212;
|
||||||
}
|
}
|
||||||
#header .button:focus {
|
#header .button:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
li.open #btn-sidemenu {
|
#header li.open .button {
|
||||||
background: #121212;
|
background: #121212;
|
||||||
border-color: #121212;
|
border-color: #121212;
|
||||||
}
|
}
|
||||||
@ -1235,6 +1240,11 @@ i.spinner {
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#header ul.dropdown-menu > li.disabled:hover > a,
|
||||||
|
#header ul.dropdown-menu > li.disabled:focus > a {
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Deploy menu customisations */
|
/* Deploy menu customisations */
|
||||||
#header ul#btn-deploy-options-submenu {
|
#header ul#btn-deploy-options-submenu {
|
||||||
width: 300px !important;
|
width: 300px !important;
|
||||||
@ -1251,3 +1261,9 @@ i.spinner {
|
|||||||
#header ul#btn-deploy-options-submenu li a > i.fa {
|
#header ul#btn-deploy-options-submenu li a > i.fa {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* User menu customisations */
|
||||||
|
#header ul#btn-usermenu-submenu li a#btn-username > .menu-label {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
15
red.js
15
red.js
@ -121,13 +121,14 @@ settings.flowFile = flowFile || settings.flowFile;
|
|||||||
|
|
||||||
RED.init(server,settings);
|
RED.init(server,settings);
|
||||||
|
|
||||||
if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
|
//if (settings.httpAdminRoot !== false && settings.httpAdminAuth) {
|
||||||
app.use(settings.httpAdminRoot,
|
// app.use(settings.httpAdminRoot,
|
||||||
express.basicAuth(function(user, pass) {
|
// express.basicAuth(function(user, pass) {
|
||||||
return user === settings.httpAdminAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpAdminAuth.pass;
|
// return user === settings.httpAdminAuth.user && crypto.createHash('md5').update(pass,'utf8').digest('hex') === settings.httpAdminAuth.pass;
|
||||||
})
|
// })
|
||||||
);
|
// );
|
||||||
}
|
//}
|
||||||
|
|
||||||
if (settings.httpNodeRoot !== false && settings.httpNodeAuth) {
|
if (settings.httpNodeRoot !== false && settings.httpNodeAuth) {
|
||||||
app.use(settings.httpNodeRoot,
|
app.use(settings.httpNodeRoot,
|
||||||
express.basicAuth(function(user, pass) {
|
express.basicAuth(function(user, pass) {
|
||||||
|
33
red/api/auth/clients.js
Normal file
33
red/api/auth/clients.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 when = require("when");
|
||||||
|
|
||||||
|
var clients = [
|
||||||
|
{id:"node-red-editor",secret:"not_available"},
|
||||||
|
{id:"node-red-admin",secret:"not_available"}
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
get: function(id) {
|
||||||
|
for (var i=0;i<clients.length;i++) {
|
||||||
|
if (clients[i].id == id) {
|
||||||
|
return when.resolve(clients[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return when.resolve(null);
|
||||||
|
}
|
||||||
|
}
|
107
red/api/auth/index.js
Normal file
107
red/api/auth/index.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 passport = require("passport");
|
||||||
|
var oauth2orize = require("oauth2orize");
|
||||||
|
|
||||||
|
var strategies = require("./strategies");
|
||||||
|
var Tokens = require("./tokens");
|
||||||
|
var Users = require("./users");
|
||||||
|
var permissions = require("./permissions");
|
||||||
|
|
||||||
|
var settings = null;
|
||||||
|
var log = require("../../log");
|
||||||
|
|
||||||
|
|
||||||
|
passport.use(strategies.bearerStrategy.BearerStrategy);
|
||||||
|
passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy);
|
||||||
|
passport.use(strategies.anonymousStrategy);
|
||||||
|
|
||||||
|
var server = oauth2orize.createServer();
|
||||||
|
|
||||||
|
server.exchange(oauth2orize.exchange.password(strategies.passwordTokenExchange));
|
||||||
|
|
||||||
|
function init(_settings) {
|
||||||
|
settings = _settings;
|
||||||
|
if (settings.adminAuth) {
|
||||||
|
Users.init(settings.adminAuth);
|
||||||
|
Tokens.init(settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function needsPermission(permission) {
|
||||||
|
return function(req,res,next) {
|
||||||
|
if (settings.adminAuth) {
|
||||||
|
return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() {
|
||||||
|
if (!req.user) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
if (permissions.hasPermission(req.user,permission)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
return res.send(401);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureClientSecret(req,res,next) {
|
||||||
|
if (!req.body.client_secret) {
|
||||||
|
req.body.client_secret = 'not_available';
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
function authenticateClient(req,res,next) {
|
||||||
|
return passport.authenticate(['oauth2-client-password'], {session: false})(req,res,next);
|
||||||
|
}
|
||||||
|
function getToken(req,res,next) {
|
||||||
|
return server.token()(req,res,next);
|
||||||
|
}
|
||||||
|
|
||||||
|
function login(req,res) {
|
||||||
|
var response = {
|
||||||
|
"type":"credentials",
|
||||||
|
"prompts":[{id:"username",type:"text",label:"Username"},{id:"password",type:"password",label:"Password"}]
|
||||||
|
}
|
||||||
|
res.json(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
function revoke(req,res) {
|
||||||
|
var token = req.body.token;
|
||||||
|
// TODO: audit log
|
||||||
|
Tokens.revoke(token).then(function() {
|
||||||
|
res.send(200);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: init,
|
||||||
|
needsPermission: needsPermission,
|
||||||
|
ensureClientSecret: ensureClientSecret,
|
||||||
|
authenticateClient: authenticateClient,
|
||||||
|
getToken: getToken,
|
||||||
|
errorHandler: function(err,req,res,next) {
|
||||||
|
//TODO: standardize json response
|
||||||
|
//TODO: audit log statment
|
||||||
|
//console.log(err.stack);
|
||||||
|
//log.log({level:"audit",type:"auth",msg:err.toString()});
|
||||||
|
return server.errorHandler()(err,req,res,next);
|
||||||
|
},
|
||||||
|
login: login,
|
||||||
|
revoke: revoke
|
||||||
|
}
|
36
red/api/auth/permissions.js
Normal file
36
red/api/auth/permissions.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 util = require('util');
|
||||||
|
|
||||||
|
var readRE = /^((.+)\.)?read$/
|
||||||
|
var writeRE = /^((.+)\.)?write$/
|
||||||
|
|
||||||
|
function hasPermission(user,permission) {
|
||||||
|
if (!user.permissions) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (user.permissions == "*") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (user.permissions == "read") {
|
||||||
|
return readRE.test(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
hasPermission: hasPermission,
|
||||||
|
}
|
116
red/api/auth/strategies.js
Normal file
116
red/api/auth/strategies.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 BearerStrategy = require('passport-http-bearer').Strategy;
|
||||||
|
var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;
|
||||||
|
|
||||||
|
var passport = require("passport");
|
||||||
|
var crypto = require("crypto");
|
||||||
|
var util = require("util");
|
||||||
|
|
||||||
|
var Tokens = require("./tokens");
|
||||||
|
var Users = require("./users");
|
||||||
|
var Clients = require("./clients");
|
||||||
|
|
||||||
|
var bearerStrategy = function (accessToken, done) {
|
||||||
|
// is this a valid token?
|
||||||
|
Tokens.get(accessToken).then(function(token) {
|
||||||
|
if (token) {
|
||||||
|
Users.get(token.user).then(function(user) {
|
||||||
|
if (user) {
|
||||||
|
done(null,user,{scope:token.scope});
|
||||||
|
} else {
|
||||||
|
done(null,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
done(null,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
bearerStrategy.BearerStrategy = new BearerStrategy(bearerStrategy);
|
||||||
|
|
||||||
|
var clientPasswordStrategy = function(clientId, clientSecret, done) {
|
||||||
|
Clients.get(clientId).then(function(client) {
|
||||||
|
if (client && client.secret == clientSecret) {
|
||||||
|
done(null,client);
|
||||||
|
} else {
|
||||||
|
done(null,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
clientPasswordStrategy.ClientPasswordStrategy = new ClientPasswordStrategy(clientPasswordStrategy);
|
||||||
|
|
||||||
|
var loginAttempts = [];
|
||||||
|
var loginSignUpWindow = 36000000; // 10 minutes
|
||||||
|
|
||||||
|
|
||||||
|
var passwordTokenExchange = function(client, username, password, scope, done) {
|
||||||
|
var now = Date.now();
|
||||||
|
loginAttempts = loginAttempts.filter(function(logEntry) {
|
||||||
|
return logEntry.time + loginSignUpWindow > now;
|
||||||
|
});
|
||||||
|
loginAttempts.push({time:now, user:username});
|
||||||
|
var attemptCount = 0;
|
||||||
|
loginAttempts.forEach(function(logEntry) {
|
||||||
|
if (logEntry.user == username) {
|
||||||
|
attemptCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (attemptCount > 5) {
|
||||||
|
// TODO: audit log
|
||||||
|
done(new Error("Too many login attempts. Wait 10 minutes and try again"),false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Users.authenticate(username,password).then(function(user) {
|
||||||
|
if (user) {
|
||||||
|
loginAttempts = loginAttempts.filter(function(logEntry) {
|
||||||
|
return logEntry.user !== username;
|
||||||
|
});
|
||||||
|
Tokens.create(username,client.id,scope).then(function(tokens) {
|
||||||
|
// TODO: audit log
|
||||||
|
done(null,tokens.accessToken);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// TODO: audit log
|
||||||
|
done(null,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function AnonymousStrategy() {
|
||||||
|
passport.Strategy.call(this);
|
||||||
|
this.name = 'anon';
|
||||||
|
}
|
||||||
|
util.inherits(AnonymousStrategy, passport.Strategy);
|
||||||
|
AnonymousStrategy.prototype.authenticate = function(req) {
|
||||||
|
var self = this;
|
||||||
|
Users.default().then(function(anon) {
|
||||||
|
if (anon) {
|
||||||
|
self.success(anon);
|
||||||
|
} else {
|
||||||
|
self.fail(401);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
bearerStrategy: bearerStrategy,
|
||||||
|
clientPasswordStrategy: clientPasswordStrategy,
|
||||||
|
passwordTokenExchange: passwordTokenExchange,
|
||||||
|
anonymousStrategy: new AnonymousStrategy()
|
||||||
|
}
|
73
red/api/auth/tokens/index.js
Normal file
73
red/api/auth/tokens/index.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 when = require("when");
|
||||||
|
var Sessions;
|
||||||
|
|
||||||
|
function generateToken(length) {
|
||||||
|
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||||
|
var token = [];
|
||||||
|
for (var i=0;i<length;i++) {
|
||||||
|
token.push(c[Math.floor(Math.random()*c.length)]);
|
||||||
|
}
|
||||||
|
return token.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var sessionModule;
|
||||||
|
|
||||||
|
function moduleSelector(aSettings) {
|
||||||
|
var toReturn;
|
||||||
|
if (aSettings.sessionStorageModule) {
|
||||||
|
if (typeof aSettings.sessionStorageModule === "string") {
|
||||||
|
// TODO: allow storage modules to be specified by absolute path
|
||||||
|
toReturn = require("./"+aSettings.sessionStorageModule);
|
||||||
|
} else {
|
||||||
|
toReturn = aSettings.sessionStorageModule;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toReturn = require("./localfilesystem");
|
||||||
|
}
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function(settings) {
|
||||||
|
sessionModule = moduleSelector(settings);
|
||||||
|
sessionModule.init(settings);
|
||||||
|
},
|
||||||
|
get: function(token) {
|
||||||
|
return sessionModule.get(token);
|
||||||
|
},
|
||||||
|
create: function(user,client,scope) {
|
||||||
|
var accessToken = generateToken(128);
|
||||||
|
var session = {
|
||||||
|
user:user,
|
||||||
|
client:client,
|
||||||
|
scope:scope,
|
||||||
|
accessToken: accessToken,
|
||||||
|
};
|
||||||
|
return sessionModule.create(accessToken,session).then(function() {
|
||||||
|
return {
|
||||||
|
accessToken: accessToken,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
revoke: function(token) {
|
||||||
|
return sessionModule.delete(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
72
red/api/auth/tokens/localfilesystem.js
Normal file
72
red/api/auth/tokens/localfilesystem.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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');
|
||||||
|
var when = require('when');
|
||||||
|
var nodeFn = require('when/node/function');
|
||||||
|
var fspath = require("path");
|
||||||
|
|
||||||
|
var settings;
|
||||||
|
var sessionsFile;
|
||||||
|
|
||||||
|
var sessions = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write content to a file using UTF8 encoding.
|
||||||
|
* This forces a fsync before completing to ensure
|
||||||
|
* the write hits disk.
|
||||||
|
*/
|
||||||
|
function writeFile(path,content) {
|
||||||
|
return when.promise(function(resolve,reject) {
|
||||||
|
var stream = fs.createWriteStream(path);
|
||||||
|
stream.on('open',function(fd) {
|
||||||
|
stream.end(content,'utf8',function() {
|
||||||
|
fs.fsync(fd,resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
stream.on('error',function(err) {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var api = module.exports = {
|
||||||
|
init: function(_settings) {
|
||||||
|
settings = _settings;
|
||||||
|
var userDir = settings.userDir || process.env.NODE_RED_HOME;
|
||||||
|
sessionsFile = fspath.join(userDir,".sessions.json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
sessions = JSON.parse(fs.readFileSync(sessionsFile,'utf8'));
|
||||||
|
} catch(err) {
|
||||||
|
sessions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return when.resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
get: function(token) {
|
||||||
|
return when.resolve(sessions[token]);
|
||||||
|
},
|
||||||
|
create: function(token,session) {
|
||||||
|
sessions[token] = session;
|
||||||
|
return writeFile(sessionsFile,JSON.stringify(sessions));
|
||||||
|
},
|
||||||
|
delete: function(token) {
|
||||||
|
delete sessions[token];
|
||||||
|
return writeFile(sessionsFile,JSON.stringify(sessions));
|
||||||
|
}
|
||||||
|
}
|
101
red/api/auth/users.js
Normal file
101
red/api/auth/users.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 when = require("when");
|
||||||
|
var util = require("util");
|
||||||
|
var bcrypt = require('bcryptjs');
|
||||||
|
|
||||||
|
var users = {};
|
||||||
|
var passwords = {};
|
||||||
|
var defaultUser = null;
|
||||||
|
|
||||||
|
function authenticate(username,password) {
|
||||||
|
var user = users[username];
|
||||||
|
if (user) {
|
||||||
|
return when.promise(function(resolve,reject) {
|
||||||
|
bcrypt.compare(password, passwords[username], function(err, res) {
|
||||||
|
resolve(res?user:null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return when.resolve(null);
|
||||||
|
}
|
||||||
|
function get(username) {
|
||||||
|
return when.resolve(users[username]);
|
||||||
|
}
|
||||||
|
function getDefaultUser() {
|
||||||
|
return when.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var api = {
|
||||||
|
get: get,
|
||||||
|
authenticate: authenticate,
|
||||||
|
default: getDefaultUser
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(config) {
|
||||||
|
users = {};
|
||||||
|
passwords = {};
|
||||||
|
defaultUser = null;
|
||||||
|
if (config.type == "credentials") {
|
||||||
|
if (config.users) {
|
||||||
|
if (typeof config.users === "function") {
|
||||||
|
api.get = config.users;
|
||||||
|
} else {
|
||||||
|
var us = config.users;
|
||||||
|
if (!util.isArray(us)) {
|
||||||
|
us = [us];
|
||||||
|
}
|
||||||
|
for (var i=0;i<us.length;i++) {
|
||||||
|
var u = us[i];
|
||||||
|
users[u.username] = {
|
||||||
|
"username":u.username,
|
||||||
|
"permissions":u.permissions
|
||||||
|
};
|
||||||
|
passwords[u.username] = u.password;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.authenticate && typeof config.authenticate === "function") {
|
||||||
|
api.authenticate = config.authenticate;
|
||||||
|
} else {
|
||||||
|
api.authenticate = authenticate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.default) {
|
||||||
|
if (typeof config.default === "function") {
|
||||||
|
api.default = config.default;
|
||||||
|
} else {
|
||||||
|
api.default = function() {
|
||||||
|
return when.resolve({
|
||||||
|
"anonymous": true,
|
||||||
|
"permissions":config.default.permissions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
api.default = getDefaultUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: init,
|
||||||
|
get: function(username) { return api.get(username) },
|
||||||
|
authenticate: function(username,password) { return api.authenticate(username,password) },
|
||||||
|
default: function() { return api.default(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -16,52 +16,78 @@
|
|||||||
|
|
||||||
var express = require("express");
|
var express = require("express");
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
var path = require('path');
|
||||||
|
var passport = require('passport');
|
||||||
|
|
||||||
var ui = require("./ui");
|
var ui = require("./ui");
|
||||||
var nodes = require("./nodes");
|
var nodes = require("./nodes");
|
||||||
var flows = require("./flows");
|
var flows = require("./flows");
|
||||||
var library = require("./library");
|
var library = require("./library");
|
||||||
|
var info = require("./info");
|
||||||
|
|
||||||
|
var auth = require("./auth");
|
||||||
|
var needsPermission = auth.needsPermission;
|
||||||
|
|
||||||
var settings = require("../settings");
|
var settings = require("../settings");
|
||||||
|
|
||||||
var errorHandler = function(err,req,res,next) {
|
var errorHandler = function(err,req,res,next) {
|
||||||
//TODO: standardize json response
|
//TODO: standardize json response
|
||||||
|
console.log(err.stack);
|
||||||
res.send(400,err.toString());
|
res.send(400,err.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
function init(adminApp) {
|
function init(adminApp) {
|
||||||
|
|
||||||
adminApp.use(express.json());
|
auth.init(settings);
|
||||||
|
|
||||||
library.init(adminApp);
|
|
||||||
|
|
||||||
// Editor
|
// Editor
|
||||||
if (!settings.disableEditor) {
|
if (!settings.disableEditor) {
|
||||||
adminApp.get("/",ui.ensureSlash);
|
var editorApp = express();
|
||||||
adminApp.get("/icons/:icon",ui.icon);
|
editorApp.get("/",ui.ensureSlash);
|
||||||
adminApp.get("/settings",ui.settings);
|
editorApp.get("/icons/:icon",ui.icon);
|
||||||
adminApp.use("/",ui.editor);
|
editorApp.use("/",ui.editor);
|
||||||
|
adminApp.use(editorApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
adminApp.use(express.json());
|
||||||
|
adminApp.use(express.urlencoded());
|
||||||
|
|
||||||
|
if (settings.adminAuth) {
|
||||||
|
//TODO: all passport references ought to be in ./auth
|
||||||
|
adminApp.use(passport.initialize());
|
||||||
|
adminApp.post("/auth/token",
|
||||||
|
auth.ensureClientSecret,
|
||||||
|
auth.authenticateClient,
|
||||||
|
auth.getToken,
|
||||||
|
auth.errorHandler
|
||||||
|
);
|
||||||
|
adminApp.get("/auth/login",auth.login);
|
||||||
|
adminApp.post("/auth/revoke",auth.revoke);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flows
|
// Flows
|
||||||
adminApp.get("/flows",flows.get);
|
adminApp.get("/flows",needsPermission("flows.read"),flows.get);
|
||||||
adminApp.post("/flows",flows.post);
|
adminApp.post("/flows",needsPermission("flows.write"),flows.post);
|
||||||
|
|
||||||
// Nodes
|
// Nodes
|
||||||
adminApp.get("/nodes",nodes.getAll);
|
adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll);
|
||||||
adminApp.post("/nodes",nodes.post);
|
adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post);
|
||||||
|
|
||||||
adminApp.get("/nodes/:mod",nodes.getModule);
|
adminApp.get("/nodes/:mod",needsPermission("nodes.read"),nodes.getModule);
|
||||||
adminApp.put("/nodes/:mod",nodes.putModule);
|
adminApp.put("/nodes/:mod",needsPermission("nodes.write"),nodes.putModule);
|
||||||
adminApp.delete("/nodes/:mod",nodes.delete);
|
adminApp.delete("/nodes/:mod",needsPermission("nodes.write"),nodes.delete);
|
||||||
|
|
||||||
adminApp.get("/nodes/:mod/:set",nodes.getSet);
|
adminApp.get("/nodes/:mod/:set",needsPermission("nodes.read"),nodes.getSet);
|
||||||
adminApp.put("/nodes/:mod/:set",nodes.putSet);
|
adminApp.put("/nodes/:mod/:set",needsPermission("nodes.write"),nodes.putSet);
|
||||||
|
|
||||||
// Library
|
// Library
|
||||||
adminApp.post(new RegExp("/library/flows\/(.*)"),library.post);
|
library.init(adminApp);
|
||||||
adminApp.get("/library/flows",library.getAll);
|
adminApp.post(new RegExp("/library/flows\/(.*)"),needsPermission("library.write"),library.post);
|
||||||
adminApp.get(new RegExp("/library/flows\/(.*)"),library.get);
|
adminApp.get("/library/flows",needsPermission("library.read"),library.getAll);
|
||||||
|
adminApp.get(new RegExp("/library/flows\/(.*)"),needsPermission("library.read"),library.get);
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
adminApp.get("/settings",needsPermission("settings.read"),info.settings);
|
||||||
|
|
||||||
// Error Handler
|
// Error Handler
|
||||||
adminApp.use(errorHandler);
|
adminApp.use(errorHandler);
|
||||||
|
27
red/api/info.js
Normal file
27
red/api/info.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 settings = require('../settings');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
settings: function(req,res) {
|
||||||
|
var safeSettings = {
|
||||||
|
httpNodeRoot: settings.httpNodeRoot,
|
||||||
|
version: settings.version,
|
||||||
|
user: req.user
|
||||||
|
};
|
||||||
|
res.json(safeSettings);
|
||||||
|
}
|
||||||
|
}
|
@ -17,10 +17,11 @@
|
|||||||
var redApp = null;
|
var redApp = null;
|
||||||
var storage = require("../storage");
|
var storage = require("../storage");
|
||||||
var log = require("../log");
|
var log = require("../log");
|
||||||
|
var needsPermission = require("./auth").needsPermission;
|
||||||
|
|
||||||
function createLibrary(type) {
|
function createLibrary(type) {
|
||||||
if (redApp) {
|
if (redApp) {
|
||||||
redApp.get(new RegExp("/library/"+type+"($|\/(.*))"),function(req,res) {
|
redApp.get(new RegExp("/library/"+type+"($|\/(.*))"),needsPermission("library.read"),function(req,res) {
|
||||||
var path = req.params[1]||"";
|
var path = req.params[1]||"";
|
||||||
storage.getLibraryEntry(type,path).then(function(result) {
|
storage.getLibraryEntry(type,path).then(function(result) {
|
||||||
if (typeof result === "string") {
|
if (typeof result === "string") {
|
||||||
@ -42,7 +43,7 @@ function createLibrary(type) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
redApp.post(new RegExp("/library/"+type+"\/(.*)"),function(req,res) {
|
redApp.post(new RegExp("/library/"+type+"\/(.*)"),needsPermission("library.write"),function(req,res) {
|
||||||
var path = req.params[0];
|
var path = req.params[0];
|
||||||
var fullBody = '';
|
var fullBody = '';
|
||||||
req.on('data', function(chunk) {
|
req.on('data', function(chunk) {
|
||||||
|
@ -55,12 +55,5 @@ module.exports = {
|
|||||||
res.sendfile(defaultIcon);
|
res.sendfile(defaultIcon);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
settings: function(req,res) {
|
|
||||||
var safeSettings = {
|
|
||||||
httpNodeRoot: settings.httpNodeRoot,
|
|
||||||
version: settings.version
|
|
||||||
};
|
|
||||||
res.json(safeSettings);
|
|
||||||
},
|
|
||||||
editor: express.static(__dirname + '/../../public')
|
editor: express.static(__dirname + '/../../public')
|
||||||
};
|
};
|
||||||
|
134
red/comms.js
134
red/comms.js
@ -21,6 +21,7 @@ var server;
|
|||||||
var settings;
|
var settings;
|
||||||
|
|
||||||
var wsServer;
|
var wsServer;
|
||||||
|
var pendingConnections = [];
|
||||||
var activeConnections = [];
|
var activeConnections = [];
|
||||||
|
|
||||||
var retained = {};
|
var retained = {};
|
||||||
@ -34,53 +35,92 @@ function init(_server,_settings) {
|
|||||||
settings = _settings;
|
settings = _settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
|
var Tokens = require("./api/auth/tokens");
|
||||||
|
var Users = require("./api/auth/users");
|
||||||
|
var Permissions = require("./api/auth/permissions");
|
||||||
|
|
||||||
if (!settings.disableEditor) {
|
if (!settings.disableEditor) {
|
||||||
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
|
Users.default().then(function(anonymousUser) {
|
||||||
var path = settings.httpAdminRoot || "/";
|
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
|
||||||
path = path + (path.slice(-1) == "/" ? "":"/") + "comms";
|
var path = settings.httpAdminRoot || "/";
|
||||||
wsServer = new ws.Server({server:server,path:path});
|
path = path + (path.slice(-1) == "/" ? "":"/") + "comms";
|
||||||
|
wsServer = new ws.Server({server:server,path:path});
|
||||||
|
|
||||||
wsServer.on('connection',function(ws) {
|
wsServer.on('connection',function(ws) {
|
||||||
activeConnections.push(ws);
|
var pendingAuth = (settings.adminAuth != null);
|
||||||
ws.on('close',function() {
|
if (!pendingAuth) {
|
||||||
for (var i=0;i<activeConnections.length;i++) {
|
activeConnections.push(ws);
|
||||||
if (activeConnections[i] === ws) {
|
} else {
|
||||||
activeConnections.splice(i,1);
|
pendingConnections.push(ws);
|
||||||
break;
|
}
|
||||||
|
ws.on('close',function() {
|
||||||
|
removeActiveConnection(ws);
|
||||||
|
removePendingConnection(ws);
|
||||||
|
});
|
||||||
|
ws.on('message', function(data,flags) {
|
||||||
|
var msg = null;
|
||||||
|
try {
|
||||||
|
msg = JSON.parse(data);
|
||||||
|
} catch(err) {
|
||||||
|
log.warn("comms received malformed message : "+err.toString());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
if (!pendingAuth) {
|
||||||
|
if (msg.subscribe) {
|
||||||
|
handleRemoteSubscription(ws,msg.subscribe);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var completeConnection = function(user,sendAck) {
|
||||||
|
if (!user || !Permissions.hasPermission(user,"status.read")) {
|
||||||
|
ws.close();
|
||||||
|
} else {
|
||||||
|
pendingAuth = false;
|
||||||
|
removePendingConnection(ws);
|
||||||
|
activeConnections.push(ws);
|
||||||
|
if (sendAck) {
|
||||||
|
ws.send(JSON.stringify({auth:"ok"}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg.auth) {
|
||||||
|
Tokens.get(msg.auth).then(function(client) {
|
||||||
|
if (client) {
|
||||||
|
Users.get(client.user).then(function(user) {
|
||||||
|
completeConnection(user,true);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
completeConnection(null,false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
completeConnection(anonymousUser,false);
|
||||||
|
//TODO: duplicated code - pull non-auth message handling out
|
||||||
|
if (msg.subscribe) {
|
||||||
|
handleRemoteSubscription(ws,msg.subscribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ws.on('error', function(err) {
|
||||||
|
log.warn("comms error : "+err.toString());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
ws.on('message', function(data,flags) {
|
|
||||||
var msg = null;
|
wsServer.on('error', function(err) {
|
||||||
try {
|
log.warn("comms server error : "+err.toString());
|
||||||
msg = JSON.parse(data);
|
|
||||||
} catch(err) {
|
|
||||||
log.warn("comms received malformed message : "+err.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg.subscribe) {
|
|
||||||
handleRemoteSubscription(ws,msg.subscribe);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ws.on('error', function(err) {
|
|
||||||
log.warn("comms error : "+err.toString());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lastSentTime = Date.now();
|
||||||
|
|
||||||
|
heartbeatTimer = setInterval(function() {
|
||||||
|
var now = Date.now();
|
||||||
|
if (now-lastSentTime > webSocketKeepAliveTime) {
|
||||||
|
publish("hb",lastSentTime);
|
||||||
|
}
|
||||||
|
}, webSocketKeepAliveTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
wsServer.on('error', function(err) {
|
|
||||||
log.warn("comms server error : "+err.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
lastSentTime = Date.now();
|
|
||||||
|
|
||||||
heartbeatTimer = setInterval(function() {
|
|
||||||
var now = Date.now();
|
|
||||||
if (now-lastSentTime > webSocketKeepAliveTime) {
|
|
||||||
publish("hb",lastSentTime);
|
|
||||||
}
|
|
||||||
}, webSocketKeepAliveTime);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +163,22 @@ function handleRemoteSubscription(ws,topic) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeActiveConnection(ws) {
|
||||||
|
for (var i=0;i<activeConnections.length;i++) {
|
||||||
|
if (activeConnections[i] === ws) {
|
||||||
|
activeConnections.splice(i,1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function removePendingConnection(ws) {
|
||||||
|
for (var i=0;i<pendingConnections.length;i++) {
|
||||||
|
if (pendingConnections[i] === ws) {
|
||||||
|
pendingConnections.splice(i,1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init:init,
|
init:init,
|
||||||
|
10
red/log.js
10
red/log.js
@ -96,6 +96,16 @@ var log = module.exports = {
|
|||||||
warn: function(msg) {
|
warn: function(msg) {
|
||||||
log.log({level:log.WARN,msg:msg});
|
log.log({level:log.WARN,msg:msg});
|
||||||
},
|
},
|
||||||
|
error: function(msg) {
|
||||||
|
log.log({level:log.ERROR,msg:msg});
|
||||||
|
},
|
||||||
|
trace: function(msg) {
|
||||||
|
log.log({level:log.TRACE,msg:msg});
|
||||||
|
},
|
||||||
|
debug: function(msg) {
|
||||||
|
log.log({level:log.DEBUG,msg:msg});
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
metric: function() {
|
metric: function() {
|
||||||
return metricsEnabled;
|
return metricsEnabled;
|
||||||
|
@ -23,6 +23,7 @@ var util = require("./util");
|
|||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
var settings = require("./settings");
|
var settings = require("./settings");
|
||||||
var credentials = require("./nodes/credentials");
|
var credentials = require("./nodes/credentials");
|
||||||
|
var auth = require("./api/auth");
|
||||||
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
|
|
||||||
@ -50,6 +51,9 @@ var RED = {
|
|||||||
comms: comms,
|
comms: comms,
|
||||||
settings:settings,
|
settings:settings,
|
||||||
util: util,
|
util: util,
|
||||||
|
auth: {
|
||||||
|
needsPermission: auth.needsPermission
|
||||||
|
},
|
||||||
version: function () {
|
version: function () {
|
||||||
var p = require(path.join(process.env.NODE_RED_HOME,"package.json"));
|
var p = require(path.join(process.env.NODE_RED_HOME,"package.json"));
|
||||||
if (fs.existsSync(path.join(process.env.NODE_RED_HOME,".git"))) {
|
if (fs.existsSync(path.join(process.env.NODE_RED_HOME,".git"))) {
|
||||||
|
@ -83,9 +83,6 @@ var persistentSettings = {
|
|||||||
userSettings = null;
|
userSettings = null;
|
||||||
globalSettings = null;
|
globalSettings = null;
|
||||||
storage = null;
|
storage = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ var when = require('when');
|
|||||||
|
|
||||||
var storageModule;
|
var storageModule;
|
||||||
var settingsAvailable;
|
var settingsAvailable;
|
||||||
|
var sessionsAvailable;
|
||||||
|
|
||||||
function moduleSelector(aSettings) {
|
function moduleSelector(aSettings) {
|
||||||
var toReturn;
|
var toReturn;
|
||||||
@ -43,6 +44,7 @@ var storageModuleInterface = {
|
|||||||
try {
|
try {
|
||||||
storageModule = moduleSelector(settings);
|
storageModule = moduleSelector(settings);
|
||||||
settingsAvailable = storageModule.hasOwnProperty("getSettings") && storageModule.hasOwnProperty("saveSettings");
|
settingsAvailable = storageModule.hasOwnProperty("getSettings") && storageModule.hasOwnProperty("saveSettings");
|
||||||
|
sessionsAvailable = storageModule.hasOwnProperty("getUserSessions") && storageModule.hasOwnProperty("saveUserSessions");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return when.reject(e);
|
return when.reject(e);
|
||||||
}
|
}
|
||||||
@ -74,6 +76,7 @@ var storageModuleInterface = {
|
|||||||
return when.resolve();
|
return when.resolve();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Library Functions */
|
/* Library Functions */
|
||||||
getAllFlows: function() {
|
getAllFlows: function() {
|
||||||
return storageModule.getAllFlows();
|
return storageModule.getAllFlows();
|
||||||
|
@ -262,7 +262,6 @@ var localfilesystem = {
|
|||||||
return writeFile(globalSettingsFile,JSON.stringify(settings,null,1));
|
return writeFile(globalSettingsFile,JSON.stringify(settings,null,1));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
getAllFlows: function() {
|
getAllFlows: function() {
|
||||||
return listFiles(libFlowsDir);
|
return listFiles(libFlowsDir);
|
||||||
},
|
},
|
||||||
|
47
test/red/api/auth/clients_spec.js
Normal file
47
test/red/api/auth/clients_spec.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 Clients = require("../../../../red/api/auth/clients");
|
||||||
|
|
||||||
|
describe("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);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
85
test/red/api/auth/index_spec.js
Normal file
85
test/red/api/auth/index_spec.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 auth = require("../../../../red/api/auth");
|
||||||
|
var Tokens = require("../../../../red/api/auth/tokens");
|
||||||
|
|
||||||
|
var settings = require("../../../../red/settings");
|
||||||
|
|
||||||
|
|
||||||
|
describe("api auth middleware",function() {
|
||||||
|
|
||||||
|
describe("ensureClientSecret", function() {
|
||||||
|
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 = { send: function(resp) {
|
||||||
|
revokeToken.restore();
|
||||||
|
|
||||||
|
resp.should.equal(200);
|
||||||
|
done();
|
||||||
|
}};
|
||||||
|
|
||||||
|
auth.revoke(req,res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("login", function() {
|
||||||
|
it("returns login details", function(done) {
|
||||||
|
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();
|
||||||
|
}});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
38
test/red/api/auth/permissions_spec.js
Normal file
38
test/red/api/auth/permissions_spec.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 permissions = require("../../../../red/api/auth/permissions");
|
||||||
|
|
||||||
|
|
||||||
|
describe("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({permissions:"*"},"read").should.be.true;
|
||||||
|
permissions.hasPermission({permissions:"*"},"write").should.be.true;
|
||||||
|
});
|
||||||
|
it('a user with read permissions',function() {
|
||||||
|
permissions.hasPermission({permissions:"read"},"read").should.be.true;
|
||||||
|
permissions.hasPermission({permissions:"read"},"node.read").should.be.true;
|
||||||
|
permissions.hasPermission({permissions:"read"},"write").should.be.false;
|
||||||
|
permissions.hasPermission({permissions:"read"},"node.write").should.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
214
test/red/api/auth/strategies_spec.js
Normal file
214
test/red/api/auth/strategies_spec.js
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 strategies = require("../../../../red/api/auth/strategies");
|
||||||
|
var Users = require("../../../../red/api/auth/users");
|
||||||
|
var Tokens = require("../../../../red/api/auth/tokens");
|
||||||
|
var Clients = require("../../../../red/api/auth/clients");
|
||||||
|
|
||||||
|
|
||||||
|
describe("Auth strategies", function() {
|
||||||
|
describe("Password Token Exchange", function() {
|
||||||
|
|
||||||
|
var userAuthentication;
|
||||||
|
afterEach(function() {
|
||||||
|
if (userAuthentication) {
|
||||||
|
userAuthentication.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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('Creates new token on authentication success',function(done) {
|
||||||
|
userAuthentication = sinon.stub(Users,"authenticate",function(username,password) {
|
||||||
|
return when.resolve({username:"user"});
|
||||||
|
});
|
||||||
|
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","scope",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","scope");
|
||||||
|
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;
|
||||||
|
userDefault.restore();
|
||||||
|
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;
|
||||||
|
userDefault.restore();
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
strategies.anonymousStrategy.authenticate({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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 testClient = {id:"node-red-editor",secret:"not_available"};
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
147
test/red/api/auth/tokens/index_spec.js
Normal file
147
test/red/api/auth/tokens/index_spec.js
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 Tokens = require("../../../../../red/api/auth/tokens");
|
||||||
|
|
||||||
|
|
||||||
|
describe("Tokens", function() {
|
||||||
|
describe("#init",function() {
|
||||||
|
var module = require("module");
|
||||||
|
var originalLoader;
|
||||||
|
beforeEach(function() {
|
||||||
|
originalLoader = module._load;
|
||||||
|
});
|
||||||
|
afterEach(function() {
|
||||||
|
module._load = originalLoader;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads default storage plugin', function(done) {
|
||||||
|
module._load = function(name) {
|
||||||
|
name.should.equal("./localfilesystem");
|
||||||
|
return {init: function(settings) {done()}};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Tokens.init({});
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('loads the specified storage plugin', function(done) {
|
||||||
|
module._load = function(name) {
|
||||||
|
name.should.equal("./aTestExample");
|
||||||
|
return {init: function(settings) {done()}};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Tokens.init({sessionStorageModule:"aTestExample"});
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the provided storage plugin', function(done) {
|
||||||
|
Tokens.init({sessionStorageModule:{init:function(settings){done()}}});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe("#get",function() {
|
||||||
|
it('returns a valid token', function(done) {
|
||||||
|
Tokens.init({sessionStorageModule:{
|
||||||
|
init:function(settings){},
|
||||||
|
get: function(token) {
|
||||||
|
return when.resolve({user:"fred"});
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
|
||||||
|
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({sessionStorageModule:{
|
||||||
|
init:function(settings){},
|
||||||
|
get: function(token) {
|
||||||
|
return when.resolve(null);
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
|
||||||
|
Tokens.get("1234").then(function(token) {
|
||||||
|
try {
|
||||||
|
should.not.exist(token);
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#create",function() {
|
||||||
|
it('creates a token', function(done) {
|
||||||
|
var sessionStorageModule = {
|
||||||
|
init:function(settings){},
|
||||||
|
create: sinon.stub().returns(when.resolve())
|
||||||
|
};
|
||||||
|
Tokens.init({sessionStorageModule:sessionStorageModule});
|
||||||
|
Tokens.create("user","client","scope").then(function(token) {
|
||||||
|
try {
|
||||||
|
sessionStorageModule.create.called.should.be.true;
|
||||||
|
token.should.have.a.property('accessToken',sessionStorageModule.create.args[0][0]);
|
||||||
|
sessionStorageModule.create.args[0][1].should.have.a.property('user','user');
|
||||||
|
sessionStorageModule.create.args[0][1].should.have.a.property('client','client');
|
||||||
|
sessionStorageModule.create.args[0][1].should.have.a.property('scope','scope');
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#revoke", function() {
|
||||||
|
it('revokes a token', function(done) {
|
||||||
|
var deletedToken;
|
||||||
|
Tokens.init({sessionStorageModule:{
|
||||||
|
init:function(settings){},
|
||||||
|
delete: function(token) {
|
||||||
|
deletedToken = token;
|
||||||
|
return when.resolve(null);
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
|
||||||
|
Tokens.revoke("1234").then(function() {
|
||||||
|
try {
|
||||||
|
deletedToken.should.equal("1234");
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
96
test/red/api/auth/tokens/localfilesystem_spec.js
Normal file
96
test/red/api/auth/tokens/localfilesystem_spec.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 fs = require('fs-extra');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var localfilesystem = require("../../../../../red/api/auth/tokens/localfilesystem.js");
|
||||||
|
|
||||||
|
|
||||||
|
describe("Tokens localfilesystem", function() {
|
||||||
|
var userDir = path.join(__dirname,".testUserHome");
|
||||||
|
beforeEach(function(done) {
|
||||||
|
fs.remove(userDir,function(err) {
|
||||||
|
fs.mkdir(userDir,done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterEach(function(done) {
|
||||||
|
fs.remove(userDir,done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initialise when no session file exists",function(done) {
|
||||||
|
localfilesystem.init({userDir:userDir}).then(function() {
|
||||||
|
localfilesystem.get("1234").then(function(token) {
|
||||||
|
should.not.exist(token);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initialises when session file exists", function(done) {
|
||||||
|
var sessions = {"1234":{"user":"nol","client":"node-red-admin","scope":["*"],"accessToken":"1234"}};
|
||||||
|
fs.writeFileSync(path.join(userDir,".sessions.json"),JSON.stringify(sessions),"utf8");
|
||||||
|
|
||||||
|
localfilesystem.init({userDir:userDir}).then(function() {
|
||||||
|
localfilesystem.get("1234").then(function(token) {
|
||||||
|
token.should.eql(sessions['1234']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("writes new tokens to the session file",function(done) {
|
||||||
|
var sessions = {"1234":{"user":"nol","client":"node-red-admin","scope":["*"],"accessToken":"1234"}};
|
||||||
|
fs.writeFileSync(path.join(userDir,".sessions.json"),JSON.stringify(sessions),"utf8");
|
||||||
|
|
||||||
|
localfilesystem.init({userDir:userDir}).then(function() {
|
||||||
|
localfilesystem.create("5678",{
|
||||||
|
user:"fred",
|
||||||
|
client:"client",
|
||||||
|
scope:["read"],
|
||||||
|
accessToken:"5678"
|
||||||
|
}).then(function() {
|
||||||
|
var newSessions = JSON.parse(fs.readFileSync(path.join(userDir,".sessions.json"),"utf8"));
|
||||||
|
newSessions.should.have.a.property("1234");
|
||||||
|
newSessions.should.have.a.property("5678");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deletes tokens from the session file",function(done) {
|
||||||
|
var sessions = {
|
||||||
|
"1234":{"user":"nol","client":"node-red-admin","scope":["*"],"accessToken":"1234"},
|
||||||
|
"5678":{"user":"fred","client":"client","scope":["read"],"accessToken":"5678"}
|
||||||
|
};
|
||||||
|
fs.writeFileSync(path.join(userDir,".sessions.json"),JSON.stringify(sessions),"utf8");
|
||||||
|
|
||||||
|
localfilesystem.init({userDir:userDir}).then(function() {
|
||||||
|
localfilesystem.delete("5678").then(function() {
|
||||||
|
var newSessions = JSON.parse(fs.readFileSync(path.join(userDir,".sessions.json"),"utf8"));
|
||||||
|
newSessions.should.have.a.property("1234");
|
||||||
|
newSessions.should.not.have.a.property("5678");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
186
test/red/api/auth/users_spec.js
Normal file
186
test/red/api/auth/users_spec.js
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 Users = require("../../../../red/api/auth/users");
|
||||||
|
|
||||||
|
describe("Users", function() {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@ -20,7 +20,6 @@ var express = require('express');
|
|||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
var when = require('when');
|
var when = require('when');
|
||||||
|
|
||||||
var app = express();
|
|
||||||
var redNodes = require("../../../red/nodes");
|
var redNodes = require("../../../red/nodes");
|
||||||
|
|
||||||
var flows = require("../../../red/api/flows");
|
var flows = require("../../../red/api/flows");
|
||||||
|
@ -47,12 +47,11 @@ describe("api index", function() {
|
|||||||
.get("/icons/default.png")
|
.get("/icons/default.png")
|
||||||
.expect(404,done)
|
.expect(404,done)
|
||||||
});
|
});
|
||||||
it('does not serve settings', function(done) {
|
it('serves settings', function(done) {
|
||||||
request(app)
|
request(app)
|
||||||
.get("/settings")
|
.get("/settings")
|
||||||
.expect(404,done)
|
.expect(200,done)
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("enables editor", function() {
|
describe("enables editor", function() {
|
||||||
|
60
test/red/api/info_spec.js
Normal file
60
test/red/api/info_spec.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2014 IBM Corp.
|
||||||
|
*
|
||||||
|
* 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 request = require('supertest');
|
||||||
|
var express = require('express');
|
||||||
|
var sinon = require('sinon');
|
||||||
|
var when = require('when');
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
var settings = require("../../../red/settings");
|
||||||
|
var info = require("../../../red/api/info");
|
||||||
|
|
||||||
|
describe("info api", function() {
|
||||||
|
describe("settings handler", function() {
|
||||||
|
before(function() {
|
||||||
|
var userSettings = {
|
||||||
|
foo: 123,
|
||||||
|
httpNodeRoot: "testHttpNodeRoot",
|
||||||
|
version: "testVersion"
|
||||||
|
}
|
||||||
|
settings.init(userSettings);
|
||||||
|
app = express();
|
||||||
|
app.get("/settings",info.settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function() {
|
||||||
|
settings.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the filtered settings', function(done) {
|
||||||
|
request(app)
|
||||||
|
.get("/settings")
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err,res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
|
||||||
|
res.body.should.have.property("version","testVersion");
|
||||||
|
res.body.should.not.have.property("foo",123);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -24,6 +24,7 @@ var app = express();
|
|||||||
var RED = require("../../../red/red.js");
|
var RED = require("../../../red/red.js");
|
||||||
var storage = require("../../../red/storage");
|
var storage = require("../../../red/storage");
|
||||||
var library = require("../../../red/api/library");
|
var library = require("../../../red/api/library");
|
||||||
|
var auth = require("../../../red/api/auth");
|
||||||
|
|
||||||
describe("library api", function() {
|
describe("library api", function() {
|
||||||
|
|
||||||
@ -166,6 +167,7 @@ describe("library api", function() {
|
|||||||
app = express();
|
app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
library.init(app);
|
library.init(app);
|
||||||
|
auth.init({});
|
||||||
RED.library.register("test");
|
RED.library.register("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ var express = require('express');
|
|||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
var when = require('when');
|
var when = require('when');
|
||||||
|
|
||||||
var app = express();
|
|
||||||
var redNodes = require("../../../red/nodes");
|
var redNodes = require("../../../red/nodes");
|
||||||
var server = require("../../../red/server");
|
var server = require("../../../red/server");
|
||||||
var settings = require("../../../red/settings");
|
var settings = require("../../../red/settings");
|
||||||
|
@ -20,7 +20,6 @@ var express = require("express");
|
|||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
|
|
||||||
var settings = require("../../../red/settings");
|
|
||||||
var events = require("../../../red/events");
|
var events = require("../../../red/events");
|
||||||
var ui = require("../../../red/api/ui");
|
var ui = require("../../../red/api/ui");
|
||||||
|
|
||||||
@ -135,39 +134,6 @@ describe("ui api", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("settings handler", function() {
|
|
||||||
before(function() {
|
|
||||||
var userSettings = {
|
|
||||||
foo: 123,
|
|
||||||
httpNodeRoot: "testHttpNodeRoot",
|
|
||||||
version: "testVersion"
|
|
||||||
}
|
|
||||||
settings.init(userSettings);
|
|
||||||
app = express();
|
|
||||||
app.get("/settings",ui.settings);
|
|
||||||
//app.use("/",ui.editor);
|
|
||||||
});
|
|
||||||
|
|
||||||
after(function() {
|
|
||||||
settings.reset();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns the filtered settings', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get("/settings")
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err,res) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
|
|
||||||
res.body.should.have.property("version","testVersion");
|
|
||||||
res.body.should.not.have.property("foo",123);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("editor ui handler", function() {
|
describe("editor ui handler", function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
app = express();
|
app = express();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2014 IBM Corp.
|
* Copyright 2014, 2015 IBM Corp.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,12 +15,18 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
var should = require("should");
|
var should = require("should");
|
||||||
|
var sinon = require("sinon");
|
||||||
|
|
||||||
|
var when = require("when");
|
||||||
var http = require('http');
|
var http = require('http');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var app = express();
|
var app = express();
|
||||||
var WebSocket = require('ws');
|
var WebSocket = require('ws');
|
||||||
|
|
||||||
var comms = require("../../red/comms.js");
|
var comms = require("../../red/comms.js");
|
||||||
|
var Users = require("../../red/api/auth/users");
|
||||||
|
var Tokens = require("../../red/api/auth/tokens");
|
||||||
|
|
||||||
var address = '127.0.0.1';
|
var address = '127.0.0.1';
|
||||||
var listenPort = 0; // use ephemeral port
|
var listenPort = 0; // use ephemeral port
|
||||||
|
|
||||||
@ -189,4 +195,151 @@ describe("comms", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('authentication required, no anonymous',function() {
|
||||||
|
var server;
|
||||||
|
var url;
|
||||||
|
var port;
|
||||||
|
var getDefaultUser;
|
||||||
|
var getUser;
|
||||||
|
var getToken;
|
||||||
|
before(function(done) {
|
||||||
|
getDefaultUser = sinon.stub(Users,"default",function() { return when.resolve(null);});
|
||||||
|
getUser = sinon.stub(Users,"get", function(username) {
|
||||||
|
if (username == "fred") {
|
||||||
|
return when.resolve({permissions:"read"});
|
||||||
|
} else {
|
||||||
|
return when.resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getToken = sinon.stub(Tokens,"get",function(token) {
|
||||||
|
if (token == "1234") {
|
||||||
|
return when.resolve({user:"fred"});
|
||||||
|
} else if (token == "5678") {
|
||||||
|
return when.resolve({user:"barney"});
|
||||||
|
} else {
|
||||||
|
return when.resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
server = http.createServer(function(req,res){app(req,res)});
|
||||||
|
comms.init(server, {adminAuth:{}});
|
||||||
|
server.listen(listenPort, address);
|
||||||
|
server.on('listening', function() {
|
||||||
|
port = server.address().port;
|
||||||
|
url = 'http://' + address + ':' + port + '/comms';
|
||||||
|
comms.start();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
getDefaultUser.restore();
|
||||||
|
getUser.restore();
|
||||||
|
getToken.restore();
|
||||||
|
comms.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents connections that do not authenticate',function(done) {
|
||||||
|
var ws = new WebSocket(url);
|
||||||
|
var count = 0;
|
||||||
|
var interval;
|
||||||
|
ws.on('open', function() {
|
||||||
|
ws.send('{"subscribe":"foo"}');
|
||||||
|
});
|
||||||
|
ws.on('close', function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows connections that do authenticate',function(done) {
|
||||||
|
var ws = new WebSocket(url);
|
||||||
|
var received = 0;
|
||||||
|
ws.on('open', function() {
|
||||||
|
ws.send('{"auth":"1234"}');
|
||||||
|
});
|
||||||
|
ws.on('message', function(msg) {
|
||||||
|
received++;
|
||||||
|
if (received == 1) {
|
||||||
|
msg.should.equal('{"auth":"ok"}');
|
||||||
|
ws.send('{"subscribe":"foo"}');
|
||||||
|
comms.publish('foo', 'correct');
|
||||||
|
} else {
|
||||||
|
msg.should.equal('{"topic":"foo","data":"correct"}');
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', function() {
|
||||||
|
received.should.equal(2);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects connections for non-existant token',function(done) {
|
||||||
|
var ws = new WebSocket(url);
|
||||||
|
var received = 0;
|
||||||
|
ws.on('open', function() {
|
||||||
|
ws.send('{"auth":"2345"}');
|
||||||
|
});
|
||||||
|
ws.on('close', function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('rejects connections for invalid token',function(done) {
|
||||||
|
var ws = new WebSocket(url);
|
||||||
|
var received = 0;
|
||||||
|
ws.on('open', function() {
|
||||||
|
ws.send('{"auth":"5678"}');
|
||||||
|
});
|
||||||
|
ws.on('close', function() {
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('authentication required, anonymous enabled',function() {
|
||||||
|
var server;
|
||||||
|
var url;
|
||||||
|
var port;
|
||||||
|
var getDefaultUser;
|
||||||
|
before(function(done) {
|
||||||
|
getDefaultUser = sinon.stub(Users,"default",function() { return when.resolve({permissions:"read"});});
|
||||||
|
server = http.createServer(function(req,res){app(req,res)});
|
||||||
|
comms.init(server, {adminAuth:{}});
|
||||||
|
server.listen(listenPort, address);
|
||||||
|
server.on('listening', function() {
|
||||||
|
port = server.address().port;
|
||||||
|
url = 'http://' + address + ':' + port + '/comms';
|
||||||
|
comms.start();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
after(function() {
|
||||||
|
getDefaultUser.restore();
|
||||||
|
comms.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows anonymous connections that do not authenticate',function(done) {
|
||||||
|
var ws = new WebSocket(url);
|
||||||
|
var count = 0;
|
||||||
|
var interval;
|
||||||
|
ws.on('open', function() {
|
||||||
|
ws.send('{"subscribe":"foo"}');
|
||||||
|
setTimeout(function() {
|
||||||
|
comms.publish('foo', 'correct');
|
||||||
|
},200);
|
||||||
|
});
|
||||||
|
ws.on('message', function(msg) {
|
||||||
|
msg.should.equal('{"topic":"foo","data":"correct"}');
|
||||||
|
count++;
|
||||||
|
ws.close();
|
||||||
|
});
|
||||||
|
ws.on('close', function() {
|
||||||
|
count.should.equal(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user