Skip to content

Commit e6e8f5c

Browse files
authored
Prevent resource state import without open cfn template or empty yaml (#306)
* prevent resource state import without open cfn template * allow state insertion into empty documents
1 parent 1d5d385 commit e6e8f5c

File tree

2 files changed

+65
-3
lines changed

2 files changed

+65
-3
lines changed

src/handlers/ResourceHandler.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { randomUUID } from 'crypto';
2-
import { ServerRequestHandler } from 'vscode-languageserver';
2+
import { ResponseError, ServerRequestHandler } from 'vscode-languageserver';
33
import { RequestHandler } from 'vscode-languageserver/node';
44
import { TopLevelSection } from '../context/ContextType';
55
import { getEntityMap } from '../context/SectionContextBuilder';
6+
import { CloudFormationFileType } from '../document/Document';
67
import { parseResourceTypeName } from '../resourceState/ResourceStateParser';
78
import {
89
ResourceTypesResult,
@@ -79,9 +80,22 @@ export function listResourcesHandler(
7980

8081
export function importResourceStateHandler(
8182
components: ServerComponents,
82-
): ServerRequestHandler<ResourceStateParams, ResourceStateResult, never, void> {
83+
): RequestHandler<ResourceStateParams, ResourceStateResult, void> {
8384
return async (params: ResourceStateParams): Promise<ResourceStateResult> => {
8485
components.usageTracker.track(EventType.DidImportResources);
86+
const doc = components.documentManager.get(params.textDocument.uri);
87+
if (!doc) {
88+
const msg = `${params.purpose} failed: ${params.textDocument.uri} not found`;
89+
log.error(msg);
90+
throw new ResponseError(500, msg); // all open TextDocuments should be registered by protocol
91+
}
92+
93+
if (!doc.isTemplate() && doc.cfnFileType !== CloudFormationFileType.Empty) {
94+
throw new ResponseError(
95+
400,
96+
`${params.purpose} failed: ${params.textDocument.uri} is not a valid CloudFormation template`,
97+
);
98+
}
8599
return await components.resourceStateImporter.importResourceState(params);
86100
};
87101
}

tst/unit/handlers/ResourceHandler.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1+
import { stubInterface } from 'ts-sinon';
12
import { describe, it, expect, beforeEach, vi } from 'vitest';
2-
import { CancellationToken } from 'vscode-languageserver-protocol';
3+
import { CancellationToken, RequestHandler } from 'vscode-languageserver-protocol';
34
import { getEntityMap } from '../../../src/context/SectionContextBuilder';
5+
import { CloudFormationFileType, Document } from '../../../src/document/Document';
46
import {
57
getManagedResourceStackTemplateHandler,
8+
importResourceStateHandler,
69
removeResourceTypeHandler,
710
} from '../../../src/handlers/ResourceHandler';
11+
import {
12+
ResourceStateParams,
13+
ResourceStatePurpose,
14+
ResourceStateResult,
15+
} from '../../../src/resourceState/ResourceStateTypes';
816
import { GetStackTemplateParams } from '../../../src/stacks/StackRequestType';
917
import { createMockComponents } from '../../utils/MockServerComponents';
1018

@@ -156,3 +164,43 @@ describe('ResourceHandler - removeResourceTypeHandler', () => {
156164
expect(() => handler(undefined as any)).toThrow();
157165
});
158166
});
167+
168+
describe('ResourceHandler - importResourceStateHandler', () => {
169+
let mockComponents: ReturnType<typeof createMockComponents>;
170+
let handler: RequestHandler<ResourceStateParams, ResourceStateResult, void>;
171+
const params = {
172+
textDocument: { uri: 'docUri' },
173+
resourceSelections: [
174+
{
175+
resourceType: 'AWS::S3::Bucket',
176+
resourceIdentifiers: ['bucket1234'],
177+
},
178+
],
179+
purpose: ResourceStatePurpose.IMPORT,
180+
};
181+
182+
beforeEach(() => {
183+
vi.clearAllMocks();
184+
mockComponents = createMockComponents();
185+
handler = importResourceStateHandler(mockComponents);
186+
});
187+
188+
it('should throw error if document not found', async () => {
189+
mockComponents.documentManager.get.returns(undefined);
190+
191+
await expect(async () => {
192+
await handler(params, CancellationToken.None);
193+
}).rejects.toThrow('Import failed: docUri not found');
194+
});
195+
196+
it('should throw error if document is not a valid CloudFormation template', async () => {
197+
const mockDoc = stubInterface<Document>();
198+
mockDoc.isTemplate.returns(false);
199+
Object.defineProperty(mockDoc, 'cfnFileType', { value: CloudFormationFileType.Other });
200+
mockComponents.documentManager.get.returns(mockDoc);
201+
202+
await expect(async () => {
203+
await handler(params, CancellationToken.None);
204+
}).rejects.toThrow('Import failed: docUri is not a valid CloudFormation template');
205+
});
206+
});

0 commit comments

Comments
 (0)