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

fix: expand server settings returned by LanguageClientMiddleware [HEAD-975] #392

Merged
merged 4 commits into from
Nov 20, 2023
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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Snyk Security - Code and Open Source Dependencies Changelog

## [1.26.1]

Comment on lines +3 to +4
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this version 🤔 Where can I validate it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be the next version (minor) after the currently released.

### Fixed

- Expanded the server settings returned by `LanguageClientMiddleware` to include necessary attributes for consistent initialization across the application.

### Added

- Introduced the `defaultToTrue` utility function within `LanguageServerSettings` to treat undefined feature flags as enabled by default.

### Changed

- Enhanced the `ServerSettings` type to include user-specific attributes such as `integrationName`, `integrationVersion`, `automaticAuthentication`, and `deviceId`. This unification simplifies the configuration management.

### BREAKING CHANGES

- The `fromConfiguration` method in `LanguageServerSettings` now requires a `User` object to initialize server settings, impacting all areas of the application where server settings are consumed.
- `LanguageClientMiddleware` instantiation now requires a `User` object, aligning with new server settings structure. Consumers must now pass a `User` object upon middleware creation.

## [1.25.1]

### Changed
Expand Down
21 changes: 6 additions & 15 deletions src/snyk/common/languageServer/languageServer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import _ from 'lodash';
import { firstValueFrom, ReplaySubject, Subject } from 'rxjs';
import { IAuthenticationService } from '../../base/services/authenticationService';
import { CLI_INTEGRATION_NAME } from '../../cli/contants/integration';
import { Configuration, IConfiguration } from '../configuration/configuration';
import { IConfiguration } from '../configuration/configuration';
import {
SNYK_ADD_TRUSTED_FOLDERS,
SNYK_CLI_PATH,
Expand All @@ -22,9 +21,8 @@ import { IVSCodeWindow } from '../vscode/window';
import { IVSCodeWorkspace } from '../vscode/workspace';
import { LsExecutable } from './lsExecutable';
import { LanguageClientMiddleware } from './middleware';
import { InitializationOptions, LanguageServerSettings } from './settings';
import { LanguageServerSettings, ServerSettings } from './settings';
import { CodeIssueData, IacIssueData, OssIssueData, Scan } from './types';
import * as fs from 'fs';

export interface ILanguageServer {
start(): Promise<void>;
Expand Down Expand Up @@ -111,7 +109,7 @@ export class LanguageServer implements ILanguageServer {
synchronize: {
configurationSection: CONFIGURATION_IDENTIFIER,
},
middleware: new LanguageClientMiddleware(this.configuration),
middleware: new LanguageClientMiddleware(this.configuration, this.user),
/**
* We reuse the output channel here as it's not properly disposed of by the language client ([email protected])
* See: https://github.com/microsoft/vscode-languageserver-node/blob/cdf4d6fdaefe329ce417621cf0f8b14e0b9bb39d/client/src/common/client.ts#L2789
Expand Down Expand Up @@ -178,16 +176,9 @@ export class LanguageServer implements ILanguageServer {

// Initialization options are not semantically equal to server settings, thus separated here
// https://github.com/microsoft/language-server-protocol/issues/567
async getInitializationOptions(): Promise<InitializationOptions> {
const settings = await LanguageServerSettings.fromConfiguration(this.configuration);

return {
...settings,
integrationName: CLI_INTEGRATION_NAME,
integrationVersion: await Configuration.getVersion(),
deviceId: this.user.anonymousId,
automaticAuthentication: 'false',
};
async getInitializationOptions(): Promise<ServerSettings> {
const settings = await LanguageServerSettings.fromConfiguration(this.configuration, this.user);
return settings;
}

showOutputChannel(): void {
Expand Down
9 changes: 5 additions & 4 deletions src/snyk/common/languageServer/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IConfiguration } from '../configuration/configuration';
import {
import { IConfiguration } from '../../common/configuration/configuration';
import { User } from '../user';
import type {
CancellationToken,
bastiandoetsch marked this conversation as resolved.
Show resolved Hide resolved
ConfigurationParams,
ConfigurationRequestHandlerSignature,
Expand All @@ -18,7 +19,7 @@ export type LanguageClientWorkspaceMiddleware = Partial<WorkspaceMiddleware> & {
};

export class LanguageClientMiddleware implements Middleware {
constructor(private configuration: IConfiguration) {}
constructor(private configuration: IConfiguration, private user: User) {}

workspace: LanguageClientWorkspaceMiddleware = {
configuration: async (
Expand All @@ -39,7 +40,7 @@ export class LanguageClientMiddleware implements Middleware {
return [];
}

const serverSettings = await LanguageServerSettings.fromConfiguration(this.configuration);
const serverSettings = await LanguageServerSettings.fromConfiguration(this.configuration, this.user);
return [serverSettings];
},
};
Expand Down
94 changes: 62 additions & 32 deletions src/snyk/common/languageServer/settings.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,94 @@
import _ from 'lodash';
import { IConfiguration, SeverityFilter } from '../configuration/configuration';

export type InitializationOptions = ServerSettings & {
integrationName?: string;
integrationVersion?: string;
automaticAuthentication?: string;
deviceId?: string;
};
import { CLI_INTEGRATION_NAME } from '../../cli/contants/integration';
import { Configuration, IConfiguration, SeverityFilter } from '../configuration/configuration';
import { User } from '../user';

export type ServerSettings = {
// Feature toggles
activateSnykCodeSecurity?: string;
activateSnykCodeQuality?: string;
activateSnykOpenSource?: string;
activateSnykIac?: string;

// Endpoint path, and organization
path?: string;
cliPath?: string;
endpoint?: string;
organization?: string;

// Authentication and parameters
token?: string;
automaticAuthentication?: string;
additionalParams?: string;
path?: string;
manageBinariesAutomatically?: string;

// Reporting and telemetry
sendErrorReports?: string;
organization?: string;
enableTelemetry?: string;
manageBinariesAutomatically?: string;
cliPath?: string;
token?: string;

// Security and scanning settings
filterSeverity?: SeverityFilter;
scanningMode?: string;
insecure?: string;

// Trusted folders feature
enableTrustedFoldersFeature?: string;
trustedFolders?: string[];
insecure?: string;
scanningMode?: string;

// Snyk integration settings
integrationName?: string;
integrationVersion?: string;
deviceId?: string;
};

/**
* Transforms a boolean or undefined value into a string representation.
* It guarantees that undefined values are represented as 'true'.
* This utility is used to ensure feature flags are enabled by default
* when not explicitly set to false.
*
* @param {boolean | undefined} value - The value to transform.
* @returns {string} - The string 'true' if the value is undefined or truthy, 'false' if the value is false.
*/
export const defaultToTrue = (value: boolean | undefined): string => {
return `${value !== undefined ? value : true}`;
};

export class LanguageServerSettings {
static async fromConfiguration(configuration: IConfiguration): Promise<ServerSettings> {
static async fromConfiguration(configuration: IConfiguration, user: User): Promise<ServerSettings> {
const featuresConfiguration = configuration.getFeaturesConfiguration();

const iacEnabled = _.isUndefined(featuresConfiguration.iacEnabled) ? true : featuresConfiguration.iacEnabled;
const codeSecurityEnabled = _.isUndefined(featuresConfiguration.codeSecurityEnabled)
? true
: featuresConfiguration.codeSecurityEnabled;
const codeQualityEnabled = _.isUndefined(featuresConfiguration.codeQualityEnabled)
? true
: featuresConfiguration.codeQualityEnabled;
const iacEnabled = defaultToTrue(featuresConfiguration.iacEnabled);
const codeSecurityEnabled = defaultToTrue(featuresConfiguration.codeSecurityEnabled);
const codeQualityEnabled = defaultToTrue(featuresConfiguration.codeQualityEnabled);

return {
activateSnykCodeSecurity: `${codeSecurityEnabled}`,
activateSnykCodeQuality: `${codeQualityEnabled}`,
activateSnykCodeSecurity: codeSecurityEnabled,
activateSnykCodeQuality: codeQualityEnabled,
activateSnykOpenSource: 'false',
activateSnykIac: `${iacEnabled}`,
enableTelemetry: `${configuration.shouldReportEvents}`,
sendErrorReports: `${configuration.shouldReportErrors}`,
activateSnykIac: iacEnabled,

cliPath: configuration.getCliPath(),
endpoint: configuration.snykOssApiEndpoint,
additionalParams: configuration.getAdditionalCliParameters(),
organization: configuration.organization,

token: await configuration.getToken(),
automaticAuthentication: 'false',
additionalParams: configuration.getAdditionalCliParameters(),
manageBinariesAutomatically: `${configuration.isAutomaticDependencyManagementEnabled()}`,

sendErrorReports: `${configuration.shouldReportErrors}`,
enableTelemetry: `${configuration.shouldReportEvents}`,

filterSeverity: configuration.severityFilter,
scanningMode: configuration.scanningMode,
insecure: `${configuration.getInsecure()}`,

enableTrustedFoldersFeature: 'true',
trustedFolders: configuration.getTrustedFolders(),
insecure: `${configuration.getInsecure()}`,
scanningMode: configuration.scanningMode,

integrationName: CLI_INTEGRATION_NAME,
integrationVersion: await Configuration.getVersion(),
deviceId: user.anonymousId,
};
}
}
4 changes: 2 additions & 2 deletions src/test/unit/common/languageServer/languageServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { v4 } from 'uuid';
import { IAuthenticationService } from '../../../../snyk/base/services/authenticationService';
import { IConfiguration } from '../../../../snyk/common/configuration/configuration';
import { LanguageServer } from '../../../../snyk/common/languageServer/languageServer';
import { InitializationOptions } from '../../../../snyk/common/languageServer/settings';
import { ServerSettings } from '../../../../snyk/common/languageServer/settings';
import { DownloadService } from '../../../../snyk/common/services/downloadService';
import { User } from '../../../../snyk/common/user';
import { ILanguageClientAdapter } from '../../../../snyk/common/vscode/languageClient';
Expand Down Expand Up @@ -198,7 +198,7 @@ suite('Language Server', () => {
});

test('LanguageServer should provide correct initialization options', async () => {
const expectedInitializationOptions: InitializationOptions = {
const expectedInitializationOptions: ServerSettings = {
activateSnykCodeSecurity: 'true',
activateSnykCodeQuality: 'true',
activateSnykOpenSource: 'false',
Expand Down
10 changes: 7 additions & 3 deletions src/test/unit/common/languageServer/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { CliExecutable } from '../../../../snyk/cli/cliExecutable';
import { IConfiguration } from '../../../../snyk/common/configuration/configuration';
import { LanguageClientMiddleware } from '../../../../snyk/common/languageServer/middleware';
import { ServerSettings } from '../../../../snyk/common/languageServer/settings';
import {
import { User } from '../../../../snyk/common/user';
import type {
CancellationToken,
ConfigurationParams,
ConfigurationRequestHandlerSignature,
Expand All @@ -15,7 +16,10 @@ import { extensionContextMock } from '../../mocks/extensionContext.mock';

suite('Language Server: Middleware', () => {
let configuration: IConfiguration;
let user: User;

setup(() => {
user = { anonymousId: 'anonymous-id' } as User;
configuration = {
shouldReportEvents: false,
shouldReportErrors: false,
Expand Down Expand Up @@ -51,7 +55,7 @@ suite('Language Server: Middleware', () => {
});

test('Configuration request should translate settings', async () => {
const middleware = new LanguageClientMiddleware(configuration);
const middleware = new LanguageClientMiddleware(configuration, user);
const params: ConfigurationParams = {
items: [
{
Expand Down Expand Up @@ -96,7 +100,7 @@ suite('Language Server: Middleware', () => {
});

test('Configuration request should return an error', async () => {
const middleware = new LanguageClientMiddleware(configuration);
const middleware = new LanguageClientMiddleware(configuration, user);
const params: ConfigurationParams = {
items: [
{
Expand Down
55 changes: 55 additions & 0 deletions src/test/unit/common/languageServer/settings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import assert from 'assert';
import { IConfiguration } from '../../../../snyk/common/configuration/configuration';
import { LanguageServerSettings, defaultToTrue } from '../../../../snyk/common/languageServer/settings';
import { User } from '../../../../snyk/common/user';

suite('LanguageServerSettings', () => {
suite('defaultToTrue', () => {
test('should return "true" for undefined values', () => {
assert.strictEqual(defaultToTrue(undefined), 'true');
});

test('should return "true" for truthy values', () => {
assert.strictEqual(defaultToTrue(true), 'true');
});

test('should return "false" for false values', () => {
assert.strictEqual(defaultToTrue(false), 'false');
});
});

suite('fromConfiguration', () => {
test('should generate server settings with default true values for undefined feature toggles', async () => {
const mockUser = { anonymousId: 'anonymous-id' } as User;
const mockConfiguration: IConfiguration = {
shouldReportEvents: true,
shouldReportErrors: false,
snykOssApiEndpoint: 'https://dev.snyk.io/api',
organization: 'my-org',
// eslint-disable-next-line @typescript-eslint/require-await
getToken: async () => 'snyk-token',
getFeaturesConfiguration: () => ({}), // iacEnabled, codeSecurityEnabled, codeQualityEnabled are undefined
getCliPath: () => '/path/to/cli',
getAdditionalCliParameters: () => '--all-projects -d',
getTrustedFolders: () => ['/trusted/path'],
getInsecure: () => false,
isAutomaticDependencyManagementEnabled: () => true,
severityFilter: { critical: true, high: true, medium: true, low: false },
scanningMode: 'scan-mode',
} as IConfiguration;

const serverSettings = await LanguageServerSettings.fromConfiguration(mockConfiguration, mockUser);

assert.strictEqual(serverSettings.activateSnykCodeSecurity, 'true');
assert.strictEqual(serverSettings.activateSnykCodeQuality, 'true');
assert.strictEqual(serverSettings.activateSnykIac, 'true');
assert.strictEqual(serverSettings.deviceId, 'anonymous-id');

assert.strictEqual(serverSettings.enableTelemetry, 'true');
assert.strictEqual(serverSettings.sendErrorReports, 'false');
assert.strictEqual(serverSettings.cliPath, '/path/to/cli');

assert.strictEqual(serverSettings.token, 'snyk-token');
});
});
});
Loading