Allow other credentials than just password
Makes the XVP authentication mechanism more general.
This commit is contained in:
parent
233c8b6a53
commit
430f00d6fe
14
app/ui.js
14
app/ui.js
|
@ -206,7 +206,7 @@ var UI = {
|
|||
'onNotification': UI.notification,
|
||||
'onUpdateState': UI.updateState,
|
||||
'onDisconnected': UI.disconnectFinished,
|
||||
'onPasswordRequired': UI.passwordRequired,
|
||||
'onCredentialsRequired': UI.credentials,
|
||||
'onXvpInit': UI.updateXvpButton,
|
||||
'onClipboard': UI.clipboardReceive,
|
||||
'onBell': UI.bell,
|
||||
|
@ -1067,7 +1067,7 @@ var UI = {
|
|||
UI.updateLocalCursor();
|
||||
UI.updateViewOnly();
|
||||
|
||||
UI.rfb.connect(host, port, password, path);
|
||||
UI.rfb.connect(host, port, { password: password }, path);
|
||||
},
|
||||
|
||||
disconnect: function() {
|
||||
|
@ -1127,8 +1127,8 @@ var UI = {
|
|||
* PASSWORD
|
||||
* ------v------*/
|
||||
|
||||
passwordRequired: function(rfb, msg) {
|
||||
|
||||
credentials: function(rfb, types) {
|
||||
// FIXME: handle more types
|
||||
document.getElementById('noVNC_password_dlg')
|
||||
.classList.add('noVNC_open');
|
||||
|
||||
|
@ -1136,9 +1136,7 @@ var UI = {
|
|||
document.getElementById('noVNC_password_input').focus();
|
||||
}, 100);
|
||||
|
||||
if (typeof msg === 'undefined') {
|
||||
msg = _("Password is required");
|
||||
}
|
||||
var msg = _("Password is required");
|
||||
Log.Warn(msg);
|
||||
UI.showStatus(msg, "warning");
|
||||
},
|
||||
|
@ -1148,7 +1146,7 @@ var UI = {
|
|||
var password = inputElem.value;
|
||||
// Clear the input after reading the password
|
||||
inputElem.value = "";
|
||||
UI.rfb.sendPassword(password);
|
||||
UI.rfb.sendCredentials({ password: password });
|
||||
UI.reconnect_password = password;
|
||||
document.getElementById('noVNC_password_dlg')
|
||||
.classList.remove('noVNC_open');
|
||||
|
|
41
core/rfb.js
41
core/rfb.js
|
@ -36,7 +36,7 @@ export default function RFB(defaults) {
|
|||
|
||||
this._rfb_host = '';
|
||||
this._rfb_port = 5900;
|
||||
this._rfb_password = '';
|
||||
this._rfb_credentials = {};
|
||||
this._rfb_path = '';
|
||||
|
||||
this._rfb_connection_state = '';
|
||||
|
@ -124,7 +124,6 @@ export default function RFB(defaults) {
|
|||
'local_cursor': false, // Request locally rendered cursor
|
||||
'shared': true, // Request shared mode
|
||||
'view_only': false, // Disable client mouse/keyboard
|
||||
'xvp_password_sep': '@', // Separator for XVP password fields
|
||||
'disconnectTimeout': 3, // Time (s) to wait for disconnection
|
||||
'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection
|
||||
'repeaterID': '', // [UltraVNC] RepeaterID to connect to
|
||||
|
@ -134,7 +133,7 @@ export default function RFB(defaults) {
|
|||
'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate): connection state change
|
||||
'onNotification': function () { }, // onNotification(rfb, msg, level, options): notification for UI
|
||||
'onDisconnected': function () { }, // onDisconnected(rfb, reason): disconnection finished
|
||||
'onPasswordRequired': function () { }, // onPasswordRequired(rfb, msg): VNC password is required
|
||||
'onCredentialsRequired': function () { }, // onCredentialsRequired(rfb, types): VNC credentials are required
|
||||
'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
|
||||
'onBell': function () { }, // onBell(rfb): RFB Bell message received
|
||||
'onFBUReceive': function () { }, // onFBUReceive(rfb, rect): RFB FBU rect received but not yet processed
|
||||
|
@ -241,10 +240,10 @@ export default function RFB(defaults) {
|
|||
|
||||
RFB.prototype = {
|
||||
// Public methods
|
||||
connect: function (host, port, password, path) {
|
||||
connect: function (host, port, creds, path) {
|
||||
this._rfb_host = host;
|
||||
this._rfb_port = port;
|
||||
this._rfb_password = (password !== undefined) ? password : "";
|
||||
this._rfb_credentials = (creds !== undefined) ? creds : {};
|
||||
this._rfb_path = (path !== undefined) ? path : "";
|
||||
|
||||
if (!this._rfb_host) {
|
||||
|
@ -264,8 +263,8 @@ RFB.prototype = {
|
|||
this._sock.off('open');
|
||||
},
|
||||
|
||||
sendPassword: function (passwd) {
|
||||
this._rfb_password = passwd;
|
||||
sendCredentials: function (creds) {
|
||||
this._rfb_credentials = creds;
|
||||
setTimeout(this._init_msg.bind(this), 0);
|
||||
},
|
||||
|
||||
|
@ -848,21 +847,18 @@ RFB.prototype = {
|
|||
|
||||
// authentication
|
||||
_negotiate_xvp_auth: function () {
|
||||
var xvp_sep = this._xvp_password_sep;
|
||||
var xvp_auth = this._rfb_password.split(xvp_sep);
|
||||
if (xvp_auth.length < 3) {
|
||||
var msg = 'XVP credentials required (user' + xvp_sep +
|
||||
'target' + xvp_sep + 'password) -- got only ' + this._rfb_password;
|
||||
this._onPasswordRequired(this, msg);
|
||||
if (!this._rfb_credentials.username ||
|
||||
!this._rfb_credentials.password ||
|
||||
!this._rfb_credentials.target) {
|
||||
this._onCredentialsRequired(this, ["username", "password", "target"]);
|
||||
return false;
|
||||
}
|
||||
|
||||
var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
|
||||
String.fromCharCode(xvp_auth[1].length) +
|
||||
xvp_auth[0] +
|
||||
xvp_auth[1];
|
||||
var xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
|
||||
String.fromCharCode(this._rfb_credentials.target.length) +
|
||||
this._rfb_credentials.username +
|
||||
this._rfb_credentials.target;
|
||||
this._sock.send_string(xvp_auth_str);
|
||||
this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
|
||||
this._rfb_auth_scheme = 2;
|
||||
return this._negotiate_authentication();
|
||||
},
|
||||
|
@ -870,14 +866,14 @@ RFB.prototype = {
|
|||
_negotiate_std_vnc_auth: function () {
|
||||
if (this._sock.rQwait("auth challenge", 16)) { return false; }
|
||||
|
||||
if (this._rfb_password.length === 0) {
|
||||
this._onPasswordRequired(this);
|
||||
if (!this._rfb_credentials.password) {
|
||||
this._onCredentialsRequired(this, ["password"]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(directxman12): make genDES not require an Array
|
||||
var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
|
||||
var response = RFB.genDES(this._rfb_password, challenge);
|
||||
var response = RFB.genDES(this._rfb_credentials.password, challenge);
|
||||
this._sock.send(response);
|
||||
this._rfb_init_state = "SecurityResult";
|
||||
return true;
|
||||
|
@ -1496,7 +1492,6 @@ make_properties(RFB, [
|
|||
['touchButton', 'rw', 'int'], // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
|
||||
['scale', 'rw', 'float'], // Display area scale factor
|
||||
['viewport', 'rw', 'bool'], // Use viewport clipping
|
||||
['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
|
||||
['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
|
||||
['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
|
||||
['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
|
||||
|
@ -1506,7 +1501,7 @@ make_properties(RFB, [
|
|||
['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate): connection state change
|
||||
['onNotification', 'rw', 'func'], // onNotification(rfb, msg, level, options): notification for the UI
|
||||
['onDisconnected', 'rw', 'func'], // onDisconnected(rfb, reason): disconnection finished
|
||||
['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb, msg): VNC password is required
|
||||
['onCredentialsRequired', 'rw', 'func'], // onCredentialsRequired(rfb, types): VNC credentials are required
|
||||
['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
|
||||
['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
|
||||
['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
|
||||
|
|
25
docs/API.md
25
docs/API.md
|
@ -37,7 +37,6 @@ attribute mode is one of the following:
|
|||
| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks.
|
||||
| scale | float | RW | 1.0 | Display area scale factor
|
||||
| viewport | bool | RW | false | Use viewport clipping
|
||||
| xvp_password_sep | str | RW | '@' | Separator for XVP password fields
|
||||
| disconnectTimeout | int | RW | 3 | Time (in seconds) to wait for disconnection
|
||||
| wsProtocols | arr | RW | ['binary'] | Protocols to use in the WebSocket connection
|
||||
| repeaterID | str | RW | '' | UltraVNC RepeaterID to connect to
|
||||
|
@ -51,10 +50,10 @@ attributes, the RFB object has other methods that are available in the
|
|||
object instance.
|
||||
|
||||
| name | parameters | description
|
||||
| ------------------ | ------------------------------ | ------------
|
||||
| connect | (host, port, password, path) | Connect to the given host:port/path. Optional password and path.
|
||||
| ------------------ | ------------------------------- | ------------
|
||||
| connect | (host, port, credentials, path) | Connect to the given host:port/path. Optional credentials and path.
|
||||
| disconnect | () | Disconnect
|
||||
| sendPassword | (passwd) | Send password after onPasswordRequired callback
|
||||
| sendCredentials | (credentials) | Send credentials after onCredentialsRequired callback
|
||||
| sendCtrlAltDel | () | Send Ctrl-Alt-Del key sequence
|
||||
| xvpOp | (ver, op) | Send a XVP operation (2=shutdown, 3=reboot, 4=reset)
|
||||
| xvpShutdown | () | Send XVP shutdown.
|
||||
|
@ -74,11 +73,11 @@ The RFB object has certain events that can be hooked with callback
|
|||
functions.
|
||||
|
||||
| name | parameters | description
|
||||
| ------------------ | -------------------------- | ------------
|
||||
| --------------------- | -------------------------- | ------------
|
||||
| onUpdateState | (rfb, state, oldstate) | Connection state change (see details below)
|
||||
| onNotification | (rfb, msg, level, options) | Notification for the UI (optional options)
|
||||
| onDisconnected | (rfb, reason) | Disconnection finished with an optional reason. No reason specified means normal disconnect.
|
||||
| onPasswordRequired | (rfb, msg) | VNC password is required (use sendPassword), optionally comes with a message.
|
||||
| onCredentialsRequired | (rfb, types) | VNC credentials are required (use sendCredentials)
|
||||
| onClipboard | (rfb, text) | RFB clipboard contents received
|
||||
| onBell | (rfb) | RFB Bell message received
|
||||
| onFBUReceive | (rfb, fbu) | RFB FBU received but not yet processed (see details below)
|
||||
|
@ -103,6 +102,20 @@ created for new connections.
|
|||
| disconnecting | starting to disconnect
|
||||
| disconnected | disconnected - permanent end-state for this RFB object
|
||||
|
||||
__RFB onCredentialsRequired callback details__
|
||||
|
||||
The onCredentialsRequired callback is called when the server requests more
|
||||
credentials than was specified to connect(). The types argument is a list
|
||||
of all the credentials that are required. Currently the following are
|
||||
defined:
|
||||
|
||||
| name | description
|
||||
| -------- | ------------
|
||||
| username | User that authenticates
|
||||
| password | Password for user
|
||||
| target | String specifying target machine or session
|
||||
|
||||
|
||||
__RFB onFBUReceive and on FBUComplete callback details__
|
||||
|
||||
The onFBUReceive callback is invoked when a frame buffer update
|
||||
|
|
|
@ -104,10 +104,10 @@ RecordingPlayer.prototype = {
|
|||
this._rfb._sock.close = function () {};
|
||||
this._rfb._sock.flush = function () {};
|
||||
this._rfb._checkEvents = function () {};
|
||||
this._rfb.connect = function (host, port, password, path) {
|
||||
this._rfb.connect = function (host, port, credentials, path) {
|
||||
this._rfb_host = host;
|
||||
this._rfb_port = port;
|
||||
this._rfb_password = (password !== undefined) ? password : "";
|
||||
this._rfb_credentials = {};
|
||||
this._rfb_path = (path !== undefined) ? path : "";
|
||||
this._sock.init('binary', 'ws');
|
||||
this._rfb_connection_state = 'connecting';
|
||||
|
|
|
@ -116,18 +116,18 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#sendPassword', function () {
|
||||
describe('#sendCredentials', function () {
|
||||
beforeEach(function () { this.clock = sinon.useFakeTimers(); });
|
||||
afterEach(function () { this.clock.restore(); });
|
||||
|
||||
it('should set the rfb password properly"', function () {
|
||||
client.sendPassword('pass');
|
||||
expect(client._rfb_password).to.equal('pass');
|
||||
it('should set the rfb credentials properly"', function () {
|
||||
client.sendCredentials({ password: 'pass' });
|
||||
expect(client._rfb_credentials).to.deep.equal({ password: 'pass' });
|
||||
});
|
||||
|
||||
it('should call init_msg "soon"', function () {
|
||||
client._init_msg = sinon.spy();
|
||||
client.sendPassword('pass');
|
||||
client.sendCredentials({ password: 'pass' });
|
||||
this.clock.tick(5);
|
||||
expect(client._init_msg).to.have.been.calledOnce;
|
||||
});
|
||||
|
@ -836,21 +836,22 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
client._rfb_version = 3.8;
|
||||
});
|
||||
|
||||
it('should call the passwordRequired callback if missing a password', function () {
|
||||
client.set_onPasswordRequired(sinon.spy());
|
||||
it('should call the onCredentialsRequired callback if missing a password', function () {
|
||||
client.set_onCredentialsRequired(sinon.spy());
|
||||
send_security(2, client);
|
||||
|
||||
var challenge = [];
|
||||
for (var i = 0; i < 16; i++) { challenge[i] = i; }
|
||||
client._sock._websocket._receive_data(new Uint8Array(challenge));
|
||||
|
||||
var spy = client.get_onPasswordRequired();
|
||||
expect(client._rfb_password.length).to.equal(0);
|
||||
var spy = client.get_onCredentialsRequired();
|
||||
expect(client._rfb_credentials).to.be.empty;
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][1]).to.have.members(["password"]);
|
||||
});
|
||||
|
||||
it('should encrypt the password with DES and then send it back', function () {
|
||||
client._rfb_password = 'passwd';
|
||||
client._rfb_credentials = { password: 'passwd' };
|
||||
send_security(2, client);
|
||||
client._sock._websocket._get_sent_data(); // skip the choice of auth reply
|
||||
|
||||
|
@ -863,7 +864,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
});
|
||||
|
||||
it('should transition to SecurityResult immediately after sending the password', function () {
|
||||
client._rfb_password = 'passwd';
|
||||
client._rfb_credentials = { password: 'passwd' };
|
||||
send_security(2, client);
|
||||
|
||||
var challenge = [];
|
||||
|
@ -886,41 +887,44 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
|||
});
|
||||
|
||||
it('should fall through to standard VNC authentication upon completion', function () {
|
||||
client.set_xvp_password_sep('#');
|
||||
client._rfb_password = 'user#target#password';
|
||||
client._rfb_credentials = { username: 'user',
|
||||
target: 'target',
|
||||
password: 'password' };
|
||||
client._negotiate_std_vnc_auth = sinon.spy();
|
||||
send_security(22, client);
|
||||
expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call the passwordRequired callback if the password is missing', function() {
|
||||
client.set_onPasswordRequired(sinon.spy());
|
||||
client._rfb_password = '';
|
||||
it('should call the onCredentialsRequired callback if all credentials are missing', function() {
|
||||
client.set_onCredentialsRequired(sinon.spy());
|
||||
client._rfb_credentials = {};
|
||||
send_security(22, client);
|
||||
|
||||
var spy = client.get_onPasswordRequired();
|
||||
expect(client._rfb_password.length).to.equal(0);
|
||||
var spy = client.get_onCredentialsRequired();
|
||||
expect(client._rfb_credentials).to.be.empty;
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][1]).to.have.members(["username", "password", "target"]);
|
||||
});
|
||||
|
||||
it('should call the passwordRequired callback if the password is improperly formatted', function() {
|
||||
client.set_onPasswordRequired(sinon.spy());
|
||||
client._rfb_password = 'user@target';
|
||||
it('should call the onCredentialsRequired callback if some credentials are missing', function() {
|
||||
client.set_onCredentialsRequired(sinon.spy());
|
||||
client._rfb_credentials = { username: 'user',
|
||||
target: 'target' };
|
||||
send_security(22, client);
|
||||
|
||||
var spy = client.get_onPasswordRequired();
|
||||
var spy = client.get_onCredentialsRequired();
|
||||
expect(spy).to.have.been.calledOnce;
|
||||
expect(spy.args[0][1]).to.have.members(["username", "password", "target"]);
|
||||
});
|
||||
|
||||
it('should split the password, send the first two parts, and pass on the last part', function () {
|
||||
client.set_xvp_password_sep('#');
|
||||
client._rfb_password = 'user#target#password';
|
||||
it('should send user and target separately', function () {
|
||||
client._rfb_credentials = { username: 'user',
|
||||
target: 'target',
|
||||
password: 'password' };
|
||||
client._negotiate_std_vnc_auth = sinon.spy();
|
||||
|
||||
send_security(22, client);
|
||||
|
||||
expect(client._rfb_password).to.equal('password');
|
||||
|
||||
var expected = [22, 4, 6]; // auth selection, len user, len target
|
||||
for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); }
|
||||
|
||||
|
|
|
@ -99,10 +99,7 @@
|
|||
function updateDesktopName(rfb, name) {
|
||||
desktopName = name;
|
||||
}
|
||||
function passwordRequired(rfb, msg) {
|
||||
if (typeof msg === 'undefined') {
|
||||
msg = 'Password Required: ';
|
||||
}
|
||||
function credentials(rfb, types) {
|
||||
var html;
|
||||
|
||||
var form = document.createElement('form');
|
||||
|
@ -115,10 +112,10 @@
|
|||
document.getElementById('noVNC_status_bar').setAttribute("class", "noVNC_status_warn");
|
||||
document.getElementById('noVNC_status').innerHTML = '';
|
||||
document.getElementById('noVNC_status').appendChild(form);
|
||||
document.getElementById('noVNC_status').querySelector('label').textContent = msg;
|
||||
document.getElementById('noVNC_status').querySelector('label').textContent = 'Password Required: ';
|
||||
}
|
||||
function setPassword() {
|
||||
rfb.sendPassword(document.getElementById('password_input').value);
|
||||
rfb.sendCredentials({ password: document.getElementById('password_input').value });
|
||||
return false;
|
||||
}
|
||||
function sendCtrlAltDel() {
|
||||
|
@ -266,7 +263,7 @@
|
|||
'onUpdateState': updateState,
|
||||
'onDisconnected': disconnected,
|
||||
'onXvpInit': xvpInit,
|
||||
'onPasswordRequired': passwordRequired,
|
||||
'onCredentialsRequired': credentials,
|
||||
'onFBUComplete': FBUComplete,
|
||||
'onDesktopName': updateDesktopName});
|
||||
} catch (exc) {
|
||||
|
@ -274,7 +271,7 @@
|
|||
return; // don't continue trying to connect
|
||||
}
|
||||
|
||||
rfb.connect(host, port, password, path);
|
||||
rfb.connect(host, port, { password: password }, path);
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
|
Loading…
Reference in New Issue