Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use secure channel in authentication method #2

Merged
merged 1 commit into from
Oct 15, 2022
Merged
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
52 changes: 32 additions & 20 deletions bosdyn-client/channel.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use strict';

const process = require('node:process');
const { Buffer } = require('node:buffer');

const grpc = require('@grpc/grpc-js');

const {
RpcError,
ClientCancelledOperationError,
InvalidAppTokenError,
InvalidClientCertificateError,
Expand All @@ -13,15 +14,18 @@ const {
PermissionDeniedError,
ProxyConnectionError,
ResponseTooLargeError,
RetryableUnavailableError,
RpcError,
ServiceFailedDuringExecutionError,
ServiceUnavailableError,
TimedOutError,
TooManyRequestsError,
TransientFailureError,
UnableToConnectToRobotError,
UnauthenticatedError,
UnknownDnsNameError,
UnimplementedError,
TransientFailureError,
} = require('./exceptions.js');
UnknownDnsNameError,
} = require('./exceptions');

/**
* Set default max message length for sending and receiving to 100MB. This value is used when
Expand All @@ -35,23 +39,19 @@ const DEFAULT_MAX_MESSAGE_LENGTH = 100 * 1024 ** 2;
/**
* Plugin to refresh access token.
* @param {Function} token_cb Callable that returns an Object<app_token, user_token>
* @param {boolean} add_app_token Whether to include an app token in the metadata.
* This is necessary for compatibility with old robot software.
* @param {boolean} [add_app_token] Deprecated.
* @returns {Function}
*/
function RefreshingAccessTokenAuthMetadataPlugin(token_cb, add_app_token) {
function RefreshingAccessTokenAuthMetadataPlugin(token_cb, add_app_token = null) {
const _token_cb = token_cb;
const _add_app_token = add_app_token;
if (add_app_token !== null) {
process.emitWarning('add_app_token is deprecated for RefreshingAccessTokenAuthMetadataPlugin.', 'Do not set it');
}

return function setMetadata(context, callback) {
const { app_token, user_token } = _token_cb();
const { user_token } = _token_cb();
const metadata = new grpc.Metadata();
if (_add_app_token) {
metadata.set('authorization', `Bearer ${user_token}`);
metadata.set('x-bosdyn-apptoken', app_token);
} else {
metadata.set('authorization', `Bearer ${user_token}`);
}
metadata.set('authorization', `Bearer ${user_token}`);
return callback(null, metadata);
};
}
Expand All @@ -60,11 +60,14 @@ function RefreshingAccessTokenAuthMetadataPlugin(token_cb, add_app_token) {
* Returns credentials for establishing a secure channel. Uses previously set values on the linked Sdk and this.
* @param {string|Buffer} cert The certificate to create channel credentials.
* @param {Function} token_cb Callable that returns an Object<app_token, user_token>
* @param {boolean} add_app_token Whether to include an app token in the metadata.
* This is necessary for compatibility with old robot software.
* @param {boolean} add_app_token Deprecated.
* @returns {Object}
*/
function create_secure_channel_creds(cert, token_cb, add_app_token) {
function create_secure_channel_creds(cert, token_cb, add_app_token = null) {
if (add_app_token !== null) {
process.emitWarning('add_app_token is deprecated for create_secure_channel_creds.', 'Do not set it');
}

cert = Buffer.concat([Buffer.from(cert), Buffer.from('\0')]);
const transport_creds = grpc.credentials.createSsl(cert);
const plugin = RefreshingAccessTokenAuthMetadataPlugin(token_cb, add_app_token);
Expand Down Expand Up @@ -113,8 +116,8 @@ function create_insecure_channel(address, port, authority = null, options = {})

* The list contains the values for max allowed message length for both sending and
* receiving. If no values are provided, the default values of 100 MB are used.
* @param {?number} [max_send_message_length=104857600] Max message length allowed for message to send.
* @param {?number} [max_receive_message_length=104857600] Max message length allowed for message to receive.
* @param {?number} [max_send_message_length=104_857_600] Max message length allowed for message to send.
* @param {?number} [max_receive_message_length=104_857_600] Max message length allowed for message to receive.
* @returns {Object} Object with values for channel options.
*/
function generate_channel_options(max_send_message_length = null, max_receive_message_length = null) {
Expand Down Expand Up @@ -176,6 +179,15 @@ function translate_exception(rpc_error) {
}

if (code === grpc.status.UNAVAILABLE) {
if (msg.includes('Socket closed') || msg.includes('Connection reset by peer')) {
return new RetryableUnavailableError(msg);
}
if (msg.includes(502)) {
return new ServiceUnavailableError(msg);
}
if (msg.includes(429)) {
return new TooManyRequestsError(msg);
}
return new UnableToConnectToRobotError(msg);
}

Expand Down
32 changes: 13 additions & 19 deletions bosdyn-client/robot.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ class Robot {
}
}

get host() {
return this._name;
}

_get_token_id(username) {
return `${this.serial_number}.${username}`;
}
Expand Down Expand Up @@ -216,19 +220,12 @@ class Robot {
return this._robot_id;
}

async _should_send_app_token_on_each_request() {
const robot_id = await this.get_cached_robot_id();
const robot_software_version = robot_id.getSoftwareRelease().getVersion();
if (robot_software_version.getMajorVersion() <= 1 && robot_software_version.getMinorVersion() <= 1) return true;
return false;
}

/**
* Verify the right information exists before calling the ensure_secure_channel method.
* @param {string} service_name Name of the service in the directory.
* @param {boolean} [secure=true] Create a secure channel or not.
* @param {Array} options Options of the grpc channel.
* @returns {Promise<*>|*} Existing channel if found, or newly created channel if not found.
* @returns {Promise<*>} Existing channel if found, or newly created channel if not found.
*/
async ensure_channel(service_name, secure = true, options = []) {
const option = options.length ? options.map(x => x[0]) : null;
Expand Down Expand Up @@ -260,16 +257,13 @@ class Robot {
: this.ensure_insecure_channel(authority, options);
}

async ensure_secure_channel(authority, skip_app_token_check = false, options = []) {
ensure_secure_channel(authority, skip_app_token_check = false, options = []) {
if (authority in this.channels_by_authority) return this.channels_by_authority[authority];

const should_send_app_token = skip_app_token_check ? false : await this._should_send_app_token_on_each_request();

const creds = channel.create_secure_channel_creds(
this.cert,
() => ({ app_token: this.app_token, user_token: this.user_token }),
should_send_app_token,
);
const creds = channel.create_secure_channel_creds(this.cert, () => ({
app_token: this.app_token,
user_token: this.user_token,
}));
const channelData = channel.create_secure_channel(
this.address,
_DEFAULT_SECURE_CHANNEL_PORT,
Expand All @@ -287,8 +281,8 @@ class Robot {
ensure_insecure_channel(authority, options = []) {
if (authority in this.channels_by_authority) return this.channels_by_authority[authority];
const channelData = channel.create_insecure_channel(this.address, _DEFAULT_SECURE_CHANNEL_PORT, authority, options);
this.logger.debug(
`[ROBOT] Created channel to ${this.address} at port ${_DEFAULT_SECURE_CHANNEL_PORT} with authority ${authority}`,
this.logger.warn(
`[ROBOT] Created insecure channel to ${this.address} at port ${_DEFAULT_SECURE_CHANNEL_PORT} with authority ${authority}`,
);
this.channels_by_authority[authority] = channelData;
return channelData;
Expand All @@ -297,7 +291,7 @@ class Robot {
async authenticate(username, password, timeout) {
console.log('Pensez à modifier authenticate dans le fichier robot.js');
const default_service_name = AuthClient.default_service_name;
const auth_channel = this.ensure_insecure_channel(this._bootstrap_service_authorities[default_service_name]);
const auth_channel = this.ensure_secure_channel(this._bootstrap_service_authorities[default_service_name]);
const auth_client = await this.ensure_client(default_service_name, auth_channel);
const user_token = await auth_client.auth(username, password, this.app_token, { timeout });
this.update_user_token(user_token, username);
Expand Down