Skip to content

Commit

Permalink
Add policy engine resource path environment variable
Browse files Browse the repository at this point in the history
  • Loading branch information
wcalderipe committed Mar 20, 2024
1 parent 33ae82c commit cd18f44
Show file tree
Hide file tree
Showing 75 changed files with 4,152 additions and 124 deletions.
2 changes: 2 additions & 0 deletions apps/policy-engine/.env.default
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ ENGINE_UID="local-dev-engine-instance-1"

MASTER_PASSWORD="unsafe-local-dev-master-password"

RESOURCE_PATH=./apps/policy-engine/src/resource

KEYRING_TYPE="raw"

# MASTER_AWS_KMS_ARN="arn:aws:kms:us-east-2:728783560968:key/f6aa3ddb-47c3-4f31-977d-b93205bb23d1"
2 changes: 2 additions & 0 deletions apps/policy-engine/.env.test.default
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ MASTER_PASSWORD="unsafe-local-test-master-password"

KEYRING_TYPE="raw"

RESOURCE_PATH=./apps/policy-engine/src/resource

# MASTER_AWS_KMS_ARN="arn:aws:kms:us-east-2:728783560968:key/f6aa3ddb-47c3-4f31-977d-b93205bb23d1"
28 changes: 22 additions & 6 deletions apps/policy-engine/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"outputs": [
"{options.outputPath}"
],
"defaultConfiguration": "production",
"options": {
"target": "node",
"compiler": "tsc",
"outputPath": "dist/apps/policy-engine",
"main": "apps/policy-engine/src/main.ts",
"assets": [
"apps/policy-engine/src/open-policy-agent/resource",
"apps/policy-engine/src/open-policy-agent/core/rego"
],
"tsConfig": "apps/policy-engine/tsconfig.app.json",
"isolatedConfig": true,
"webpackConfig": "apps/policy-engine/webpack.config.js"
Expand All @@ -39,9 +45,13 @@
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"outputs": [
"{options.outputFile}"
],
"options": {
"lintFilePatterns": ["apps/policy-engine/**/*.ts"]
"lintFilePatterns": [
"apps/policy-engine/**/*.ts"
]
}
},
"test:type": {
Expand All @@ -52,15 +62,19 @@
},
"test:unit": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"outputs": [
"{workspaceRoot}/coverage/{projectRoot}"
],
"options": {
"jestConfig": "apps/policy-engine/jest.unit.ts",
"verbose": true
}
},
"test:integration": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"outputs": [
"{workspaceRoot}/coverage/{projectRoot}"
],
"options": {
"jestConfig": "apps/policy-engine/jest.integration.ts",
"verbose": true,
Expand All @@ -69,7 +83,9 @@
},
"test:e2e": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"outputs": [
"{workspaceRoot}/coverage/{projectRoot}"
],
"options": {
"jestConfig": "apps/policy-engine/jest.e2e.ts",
"verbose": true,
Expand Down
143 changes: 143 additions & 0 deletions apps/policy-engine/src/engine/__test__/e2e/tenant.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { EncryptionModuleOptionProvider } from '@narval/encryption-module'
import { HttpStatus, INestApplication } from '@nestjs/common'
import { ConfigModule, ConfigService } from '@nestjs/config'
import { Test, TestingModule } from '@nestjs/testing'
import request from 'supertest'
import { v4 as uuid } from 'uuid'
import { EngineService } from '../../../engine/core/service/engine.service'
import { Config, load } from '../../../policy-engine.config'
import { REQUEST_HEADER_API_KEY } from '../../../policy-engine.constant'
import { KeyValueRepository } from '../../../shared/module/key-value/core/repository/key-value.repository'
import { InMemoryKeyValueRepository } from '../../../shared/module/key-value/persistence/repository/in-memory-key-value.repository'
import { TestPrismaService } from '../../../shared/module/persistence/service/test-prisma.service'
import { getTestRawAesKeyring } from '../../../shared/testing/encryption.testing'
import { CreateTenantDto } from '../../../tenant/http/rest/dto/create-tenant.dto'
import { EngineModule } from '../../engine.module'
import { TenantRepository } from '../../persistence/repository/tenant.repository'

describe('Tenant', () => {
let app: INestApplication
let module: TestingModule
let testPrismaService: TestPrismaService
let tenantRepository: TenantRepository
let engineService: EngineService
let configService: ConfigService<Config, true>

const adminApiKey = 'test-admin-api-key'

beforeAll(async () => {
module = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
load: [load],
isGlobal: true
}),
EngineModule
]
})
.overrideProvider(KeyValueRepository)
.useValue(new InMemoryKeyValueRepository())
.overrideProvider(EncryptionModuleOptionProvider)
.useValue({
keyring: getTestRawAesKeyring()
})
.compile()

app = module.createNestApplication()

engineService = module.get<EngineService>(EngineService)
tenantRepository = module.get<TenantRepository>(TenantRepository)
testPrismaService = module.get<TestPrismaService>(TestPrismaService)
configService = module.get<ConfigService<Config, true>>(ConfigService)

await testPrismaService.truncateAll()

await engineService.save({
id: configService.get('engine.id', { infer: true }),
masterKey: 'unsafe-test-master-key',
adminApiKey
})

await app.init()
})

afterAll(async () => {
await testPrismaService.truncateAll()
await module.close()
await app.close()
})

describe('POST /tenants', () => {
const clientId = uuid()

const dataStoreConfiguration = {
dataUrl: 'http://some.host',
signatureUrl: 'http://some.host'
}

const payload: CreateTenantDto = {
clientId,
entityDataStore: dataStoreConfiguration,
policyDataStore: dataStoreConfiguration
}

it('creates a new tenant', async () => {
const { status, body } = await request(app.getHttpServer())
.post('/tenants')
.set(REQUEST_HEADER_API_KEY, adminApiKey)
.send(payload)
const actualTenant = await tenantRepository.findByClientId(clientId)

expect(body).toMatchObject({
clientId,
clientSecret: expect.any(String),
createdAt: expect.any(String),
updatedAt: expect.any(String),
dataStore: {
policy: {
...dataStoreConfiguration,
keys: []
},
entity: {
...dataStoreConfiguration,
keys: []
}
}
})
expect(body).toEqual({
...actualTenant,
createdAt: actualTenant?.createdAt.toISOString(),
updatedAt: actualTenant?.updatedAt.toISOString()
})
expect(status).toEqual(HttpStatus.CREATED)
})

it('responds with an error when clientId already exist', async () => {
await request(app.getHttpServer()).post('/tenants').set(REQUEST_HEADER_API_KEY, adminApiKey).send(payload)

const { status, body } = await request(app.getHttpServer())
.post('/tenants')
.set(REQUEST_HEADER_API_KEY, adminApiKey)
.send(payload)

expect(body).toEqual({
message: 'Tenant already exist',
statusCode: HttpStatus.BAD_REQUEST
})
expect(status).toEqual(HttpStatus.BAD_REQUEST)
})

it('responds with forbidden when admin api key is invalid', async () => {
const { status, body } = await request(app.getHttpServer())
.post('/tenants')
.set(REQUEST_HEADER_API_KEY, 'invalid-api-key')
.send(payload)

expect(body).toMatchObject({
message: 'Forbidden resource',
statusCode: HttpStatus.FORBIDDEN
})
expect(status).toEqual(HttpStatus.FORBIDDEN)
})
})
})
37 changes: 14 additions & 23 deletions apps/policy-engine/src/engine/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { EvaluationRequest } from '@narval/policy-engine-shared'
import { FIXTURE } from '@narval/policy-engine-shared'
import { Body, Controller, Get, Logger, Post } from '@nestjs/common'
import { generateInboundEvaluationRequest } from '../shared/testing/evaluation.testing'
import { AppService } from './app.service'
import { EvaluationService } from './core/service/evaluation.service'
import { EvaluationRequestDto } from './evaluation-request.dto'

@Controller()
export class AppController {
private logger = new Logger(AppController.name)

constructor(private readonly appService: AppService) {}
constructor(private readonly evaluationService: EvaluationService) {}

@Get()
healthcheck() {
Expand All @@ -31,34 +31,25 @@ export class AppController {
body
})

// Map the DTO into the TS type because it's nicer to deal with.
const payload: EvaluationRequest = body

const result = await this.appService.runEvaluation(payload)
this.logger.log({
message: 'Evaluation Result',
result
})

return result
return this.evaluationService.evaluate(FIXTURE.ORGANIZATION.id, body)
}

@Post('/evaluation-demo')
async evaluateDemo() {
const fakeRequest = await generateInboundEvaluationRequest()
this.logger.log({
message: 'Received evaluation',
body: fakeRequest
const evaluation = await generateInboundEvaluationRequest()
this.logger.log('Received evaluation', {
evaluation
})
const result = await this.appService.runEvaluation(fakeRequest)
this.logger.log({
message: 'Evaluation Result',
result

const response = await this.evaluationService.evaluate(FIXTURE.ORGANIZATION.id, evaluation)

this.logger.log('Evaluation respone', {
response
})

return {
request: fakeRequest,
result
request: evaluation,
response
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ApplicationException } from '../../../shared/exception/application.exception'

export class DataStoreException extends ApplicationException {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { HttpStatus, Injectable } from '@nestjs/common'
import { FileSystemDataStoreRepository } from '../../persistence/repository/file-system-data-store.repository'
import { HttpDataStoreRepository } from '../../persistence/repository/http-data-store.repository'
import { DataStoreException } from '../exception/data-store.exception'
import { DataStoreRepository } from '../repository/data-store.repository'

@Injectable()
export class DataStoreRepositoryFactory {
constructor(
private fileSystemRepository: FileSystemDataStoreRepository,
private httpRepository: HttpDataStoreRepository
) {}

getRepository(url: string): DataStoreRepository {
switch (this.getProtocol(url)) {
case 'file':
return this.fileSystemRepository
case 'http':
case 'https':
return this.httpRepository
default:
throw new DataStoreException({
message: 'Data store URL protocol not supported',
suggestedHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
context: { url }
})
}
}

private getProtocol(url: string): string {
return url.split(':')[0]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface DataStoreRepository {
fetch<Data>(url: string): Promise<Data>
}
Loading

0 comments on commit cd18f44

Please sign in to comment.