Skip to content

Commit

Permalink
feat: 🔥 [EXL-72] support storing code configuration
Browse files Browse the repository at this point in the history
support storing code configuration
  • Loading branch information
tal-rofe committed Oct 28, 2022
1 parent f1c5e53 commit 134f689
Show file tree
Hide file tree
Showing 19 changed files with 237 additions and 15 deletions.
5 changes: 4 additions & 1 deletion apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,24 @@
"class-transformer": "0.5.1",
"class-validator": "0.13.2",
"googleapis": "108.0.1",
"js-yaml": "4.1.0",
"mixpanel": "0.17.0",
"nestjs-real-ip": "2.2.0",
"passport": "0.6.0",
"passport-github2": "0.1.12",
"passport-google-oauth20": "2.0.0",
"passport-jwt": "4.0.0",
"reflect-metadata": "0.1.13",
"rxjs": "7.5.7"
"rxjs": "7.5.7",
"vm2": "3.9.11"
},
"devDependencies": {
"@compodoc/compodoc": "1.1.19",
"@nestjs/cli": "9.1.5",
"@nestjs/schematics": "9.0.3",
"@nestjs/swagger": "6.1.3",
"@types/express": "4.17.14",
"@types/js-yaml": "4.0.5",
"@types/passport-github2": "1.2.5",
"@types/passport-google-oauth20": "2.0.11",
"@types/passport-jwt": "3.0.7",
Expand Down
5 changes: 5 additions & 0 deletions apps/backend/src/models/file-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum FileType {
Json,
Yaml,
Js,
}
4 changes: 4 additions & 0 deletions apps/backend/src/modules/database/inline-policy.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,8 @@ export class DBInlinePolicyService {

return document.configuration;
}

public async setCodeConfiguration(policyId: string, input: object | null) {
await this.prisma.inlinePolicy.update({ where: { id: policyId }, data: { configuration: input } });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsString, MaxLength } from 'class-validator';

import { IsNullable } from '@/decorators/is-nullable.decorator';
import { FileType } from '@/models/file-type';

export class SetCodeConfigurationDto {
@ApiProperty({ type: String, description: 'Code configuration', example: '{ root: true }' })
@IsString()
@IsNullable()
@MaxLength(1000)
readonly code!: string | null;

@ApiProperty({ enum: FileType, description: 'The file type' })
@IsEnum(FileType)
readonly type!: FileType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { FileType } from '@/models/file-type';

export class SetCodeConfigurationContract {
constructor(
public readonly policyId: string,
public readonly code: string | null,
public readonly type: FileType,
) {}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { DeleteHandler } from './delete.handler';
import { EditLabelHandler } from './edit-label.handler';
import { SetCodeConfigurationHandler } from './set-code-configuration.handler';
import { SetFileListHandler } from './set-file-list.handler';

export const CommandHandlers = [EditLabelHandler, DeleteHandler, SetFileListHandler];
export const CommandHandlers = [
EditLabelHandler,
DeleteHandler,
SetFileListHandler,
SetCodeConfigurationHandler,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';

import { DBInlinePolicyService } from '@/modules/database/inline-policy.service';

import { SetCodeConfigurationContract } from '../contracts/set-code-configuration.contract';
import { parseInput } from '../../utils/parsers';

@CommandHandler(SetCodeConfigurationContract)
export class SetCodeConfigurationHandler implements ICommandHandler<SetCodeConfigurationContract> {
constructor(private readonly dbInlinePolicyService: DBInlinePolicyService) {}

async execute(contract: SetCodeConfigurationContract) {
const parsedCodeInput = parseInput(contract.code, contract.type);

await this.dbInlinePolicyService.setCodeConfiguration(contract.policyId, parsedCodeInput);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class CreateController {
type: CreateResponse,
})
@ApiUnauthorizedResponse({
description: 'If access token is missing or invalid',
description: 'If access token is missing or invalid, or group does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to create inline policy' })
@UseGuards(BelongingGroupGuard)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class GetConfigurationController {
type: GetConfigurationResponse,
})
@ApiUnauthorizedResponse({
description: 'If access token is missing or invalid',
description: 'If access token is missing or invalid, or policy does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to get the configuration' })
@UseGuards(BelongingInlinePolicyGuard)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class GetFileListController {
type: GetFileListResponse,
})
@ApiUnauthorizedResponse({
description: 'If access token is missing or invalid',
description: 'If access token is missing or invalid, or policy does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to get the file list' })
@UseGuards(BelongingInlinePolicyGuard)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class GetLibrariesController {
type: GetLibrariesResponse,
})
@ApiUnauthorizedResponse({
description: 'If access token is invalid or missing',
description: 'If access token is invalid or missing, or group does not belong to user',
})
@UseGuards(BelongingGroupGuard)
@Get(Routes.GET_LIBRARIES)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { DeleteController } from './delete.controller';
import { SetFileListController } from './set-file-list.controller';
import { GetFileListController } from './get-file-list.controller';
import { GetConfigurationController } from './get-configuration.controller';
import { SetCodeConfigurationController } from './set-code-configuration.controller';

@Module({
imports: [CqrsModule],
Expand All @@ -29,6 +30,7 @@ import { GetConfigurationController } from './get-configuration.controller';
SetFileListController,
GetFileListController,
GetConfigurationController,
SetCodeConfigurationController,
],
providers: [
BelongingInlinePolicyGuard,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const Routes = {
SET_FILE_LIST: 'file-list/:policy_id',
GET_FILE_LIST: 'file-list/:policy_id/:file_list_type',
GET_CONFIGURATION: 'configuration/:policy_id',
SET_CODE_CONFIGURATION: 'code-configuration/:policy_id',
};

export default Routes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Body, Controller, HttpCode, HttpStatus, Logger, Param, Patch, UseGuards } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import {
ApiBearerAuth,
ApiInternalServerErrorResponse,
ApiOkResponse,
ApiOperation,
ApiTags,
ApiUnauthorizedResponse,
} from '@nestjs/swagger';

import { CurrentUserId } from '@/decorators/current-user-id.decorator';

import Routes from './inline-policies.routes';
import { BelongingInlinePolicyGuard } from './guards/belonging-inline-policy.guard';
import { SetCodeConfigurationDto } from './classes/set-code-configuration.dto';
import { SetCodeConfigurationContract } from './commands/contracts/set-code-configuration.contract';

@ApiTags('Inline Policies')
@Controller(Routes.CONTROLLER)
export class SetCodeConfigurationController {
private readonly logger = new Logger(SetCodeConfigurationController.name);

constructor(private readonly commandBus: CommandBus) {}

@ApiOperation({ description: "Set policy's configuration using a code" })
@ApiBearerAuth('access-token')
@ApiOkResponse({ description: 'If successfully set configuration of the policy' })
@ApiUnauthorizedResponse({
description: 'If access token is either missing or invalid, or policy does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to set configuration of the policy' })
@UseGuards(BelongingInlinePolicyGuard)
@Patch(Routes.SET_CODE_CONFIGURATION)
@HttpCode(HttpStatus.OK)
public async setCodeConfiguration(
@CurrentUserId() userId: string,
@Body() setCodeConfigurationDto: SetCodeConfigurationDto,
@Param('policy_id') policyId: string,
): Promise<void> {
this.logger.log(
`Will try to set a policy's configuration with an ID: "${policyId}" for a user with an ID: "${userId}"`,
);

await this.commandBus.execute<SetCodeConfigurationContract, void>(
new SetCodeConfigurationContract(
policyId,
setCodeConfigurationDto.code,
setCodeConfigurationDto.type,
),
);

this.logger.log(
`Successfully set a policy configuration an ID: "${policyId}" for a user with an Id: "${userId}"`,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class SetFileListController {
description: 'If set the file list successfully',
})
@ApiUnauthorizedResponse({
description: 'If access token is missing or invalid',
description: 'If access token is missing or invalid, or policy does not belong to user',
})
@ApiInternalServerErrorResponse({ description: 'If failed to set the file list' })
@UseGuards(BelongingInlinePolicyGuard)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { BadRequestException } from '@nestjs/common';
import yaml from 'js-yaml';
import { VM } from 'vm2';

import { FileType } from '@/models/file-type';

const nodeVirtualMachine = new VM({ sandbox: { module: {} } });

/**
* The function receives a JSON-like input and parse it to an object
* @param input the input to parse
* @returns parsed object
*/
const parseJson = (input: string) => {
try {
const parsedObject = JSON.parse(input);

if (typeof parsedObject !== 'object') {
throw new BadRequestException();
}

return parsedObject as object;
} catch {
throw new BadRequestException();
}
};

/**
* The function receives a YAML-like input and parse it to an object
* @param input the input to parse
* @returns parsed object
*/
const parseYaml = (input: string) => {
try {
const parsedObject = yaml.load(input);

if (typeof parsedObject !== 'object') {
throw new BadRequestException();
}

return parsedObject as object;
} catch {
throw new BadRequestException();
}
};

/**
* The function receives a commonJs-exported-like input and parse it to an object
* @param input the input to parse
* @returns parsed object
*/
const parseJs = (input: string) => {
try {
const parsedObject = nodeVirtualMachine.run(input);

if (typeof parsedObject !== 'object') {
throw new BadRequestException();
}

return parsedObject as object;
} catch {
throw new BadRequestException();
}
};

/**
* The function receives an input (string, or null) and parses it to an object (or null if received null)
* @param input the input to parse
* @param type the representation type of the input
* @returns parsed object
*/
export const parseInput = (input: string | null, type: FileType) => {
if (input === null) {
return null;
}

if (type === FileType.Json) {
return parseJson(input);
}

if (type === FileType.Yaml) {
return parseYaml(input);
}

return parseJs(input);
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const Code: React.FC<IProps> = () => {
const [selectedFileTypeIndexState, setSelectedFileTypeIndexState] = useState<number>(0);

const isSaveChangesDisabled = useMemo(
() => (codeInServerState ?? '') === codeInputState,
() => (codeInServerState ?? '') === (codeInputState ?? ''),
[codeInServerState, codeInputState],
);

Expand Down Expand Up @@ -51,7 +51,7 @@ const Code: React.FC<IProps> = () => {
backendApi
.patch(`/user/inline-policies/code-configuration/${params.policyId}`, {
code: codeInputState,
type: selectedFileTypeValue,
type: selectedFileTypeValue.value,
})
.then(() => setCodeInServerState(() => codeInputState))
.catch(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const EDConfigCodeView: React.FC<IProps> = (props: React.PropsWithChildren<IProp
}

return (
<div className={classes['container']}>
<form className={classes['container']} onSubmit={props.onSubmit}>
<div className={classes['actionContainer']}>
<span className={classes['actionContainer__instruction']}>
{t('configCode.fileType')}
Expand Down Expand Up @@ -99,7 +99,7 @@ const EDConfigCodeView: React.FC<IProps> = (props: React.PropsWithChildren<IProp
extensions={[chosenExtension]}
onChange={(value) => props.onInputChange(value)}
/>
</div>
</form>
);
};

Expand Down
Loading

0 comments on commit 134f689

Please sign in to comment.