Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 68 additions & 3 deletions guacamole-common-js/src/main/webapp/modules/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var Guacamole = Guacamole || {};
* Guacamole protocol client. Given a {@link Guacamole.Tunnel},
* automatically handles incoming and outgoing Guacamole instructions via the
* provided tunnel, updating its display using one or more canvas elements.
*
*
* @constructor
* @param {!Guacamole.Tunnel} tunnel
* The tunnel to use to send and receive Guacamole instructions.
Expand Down Expand Up @@ -539,6 +539,35 @@ Guacamole.Client = function(tunnel) {

};

/**
* Sends an auth-response instruction over the tunnel, announcing an
* output stream carrying the response body for a previously-received
* auth-challenge identified by the same challengeId.
*
* @param {!string} mimetype
* The mimetype of the data which will be sent over the returned
* stream.
*
* @param {!string} challengeId
* The challenge identifier of the originating auth-challenge
* being responded to.
*
* @return {Guacamole.OutputStream}
* The created stream, or null if not connected.
*/
this.sendAuthResponse = function(mimetype, challengeId) {

// Do not send if not connected
if (!isConnected())
return null;

var stream = guac_client.createOutputStream();
tunnel.sendMessage("auth-response", stream.index, mimetype,
challengeId);
return stream;

};

/**
* Opens a new clipboard object for writing, having the given mimetype. The
* instruction necessary to create this stream will automatically be sent.
Expand Down Expand Up @@ -917,7 +946,26 @@ Guacamole.Client = function(tunnel) {
* The name of the pipe.
*/
this.onpipe = null;


/**
* Fired when an "auth-challenge" instruction is received from the
* server, announcing a stream carrying the challenge body for a
* pending authentication exchange. The handler is responsible for
* consuming the body from the given input stream and replying via
* sendAuthResponse() with the same challengeId.
*
* @event
* @param {!Guacamole.InputStream} stream
* The input stream carrying the challenge body.
*
* @param {!string} mimetype
* The mimetype of the challenge body. Identifies the auth flavor.
*
* @param {!string} challengeId
* The challenge identifier carried by the auth-challenge.
*/
this.onauthchallenge = null;

/**
* Fired when a "required" instruction is received. A required instruction
* indicates that additional parameters are required for the connection to
Expand Down Expand Up @@ -1557,7 +1605,7 @@ Guacamole.Client = function(tunnel) {
var mimetype = parameters[1];
var name = parameters[2];

// Create stream
// Create stream
if (guac_client.onpipe) {
var stream = streams[stream_index] = new Guacamole.InputStream(guac_client, stream_index);
guac_client.onpipe(stream, mimetype, name);
Expand All @@ -1569,6 +1617,23 @@ Guacamole.Client = function(tunnel) {

},

"auth-challenge": function(parameters) {

var stream_index = parseInt(parameters[0]);
var mimetype = parameters[1];
var challengeId = parameters[2];

if (guac_client.onauthchallenge) {
var stream = streams[stream_index] =
new Guacamole.InputStream(guac_client, stream_index);
guac_client.onauthchallenge(stream, mimetype, challengeId);
}
else
guac_client.sendAck(stream_index,
"Auth challenges unsupported", 0x0100);

},

"png": function(parameters) {

var channelMask = parseInt(parameters[0]);
Expand Down
3 changes: 2 additions & 1 deletion guacamole/src/main/frontend/src/app/client/clientModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ angular.module('client', [
'osk',
'rest',
'textInput',
'touch'
'touch',
'webauthn'
]);
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ <h3>{{'CLIENT.SECTION_HEADER_DEVICES' | translate}}</h3>
</div>
</div>

<!-- WebAuthn passthrough -->
<guac-web-authn ng-if="focusedClient.managedWebAuthn" client="focusedClient"></guac-web-authn>

<!-- Connection parameters which may be modified while the connection is open -->
<div class="menu-section connection-parameters" id="connection-settings" ng-if="focusedClient.protocol">
<guac-form namespace="getProtocolNamespace(focusedClient.protocol)"
Expand Down
36 changes: 36 additions & 0 deletions guacamole/src/main/frontend/src/app/client/types/ManagedClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
const ManagedFilesystem = $injector.get('ManagedFilesystem');
const ManagedFileUpload = $injector.get('ManagedFileUpload');
const ManagedShareLink = $injector.get('ManagedShareLink');
const ManagedWebAuthn = $injector.get('ManagedWebAuthn');

// Required services
const $document = $injector.get('$document');
Expand Down Expand Up @@ -293,6 +294,17 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
*/
this.deferredPipeStreamHandlers = template.deferredPipeStreamHandlers || {};

/**
* State tracking the WebAuthn passthrough relay for this client.
* Ceremony requests arrive as inbound "auth-challenge" streams
* with WebAuthn-flavored mimetypes; responses are sent back as
* outbound "auth-response" streams carrying the credential or
* error.
*
* @type ManagedWebAuthn
*/
this.managedWebAuthn = template.managedWebAuthn || null;

};

/**
Expand Down Expand Up @@ -443,6 +455,25 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',
tunnel : tunnel
});

// Set up WebAuthn passthrough and dispatch inbound auth-challenge
// streams to it. Other mimetypes are not currently expected.
managedClient.managedWebAuthn = ManagedWebAuthn.getInstance(managedClient);

client.onauthchallenge = function clientAuthChallengeReceived(
stream, mimetype, challengeId) {
if (ManagedWebAuthn.handlesMimetype(mimetype))
ManagedWebAuthn.handleChallenge(
managedClient.managedWebAuthn,
stream, mimetype, challengeId);
else {
console.warn('[ManagedClient] no handler for auth-challenge '
+ 'mimetype "' + mimetype + '" (id=' + challengeId
+ ')');
stream.sendAck('Unsupported auth-challenge mimetype',
Guacamole.Status.Code.UNSUPPORTED);
}
};

// Fire events for tunnel errors
tunnel.onerror = function tunnelError(status) {
$rootScope.$apply(function handleTunnelError() {
Expand Down Expand Up @@ -485,6 +516,11 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector',

// Connection has closed
case Guacamole.Tunnel.State.CLOSED:
// Dismiss any local authenticator UI left stranded
// by the tunnel dropping mid-ceremony.
if (managedClient.managedWebAuthn)
ManagedWebAuthn.abortAll(
managedClient.managedWebAuthn);
ManagedClientState.setConnectionState(managedClient.clientState,
ManagedClientState.ConnectionState.DISCONNECTED);
break;
Expand Down
Loading
Loading