Skip to content

Commit

Permalink
Support sign raw in the Policy Engine (#220)
Browse files Browse the repository at this point in the history
* Refactor toInput to use individual mapping functions

* Support sign raw action

Refactor Evaluation Request to use a Zod schema

* Refactor types to use Zod schema

* Add top-level test recipies

* Update README
  • Loading branch information
wcalderipe authored Apr 5, 2024
1 parent 4784695 commit d5dacde
Show file tree
Hide file tree
Showing 32 changed files with 448 additions and 453 deletions.
31 changes: 26 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@ include ./packages/policy-engine-shared/Makefile
include ./packages/transaction-request-intent/Makefile
include ./packages/signature/Makefile

# For more terminal color codes, head over to https://opensource.com/article/19/9/linux-terminal-colors
# For more terminal color codes, head over to
# https://opensource.com/article/19/9/linux-terminal-colors
TERM_NO_COLOR := \033[0m
TERM_GREEN := \033[0;32m

# == Install ==
# === Install ===

install:
npm install

install/ci:
npm ci

# == Setup ==
# === Setup ===

setup:
make install
Expand All @@ -32,15 +33,15 @@ setup:
@echo ""
@echo "${TERM_GREEN}Run 'make armory/start/dev' or/and 'make policy-engine/start/dev' to get them running.${TERM_NO_COLOR}"

# == Docker ==
# === Docker ===

docker/stop:
docker-compose stop

docker/up:
docker-compose up --detach

# == Code format ==
# === Code format ===

format:
npx nx format:write --all
Expand All @@ -55,3 +56,23 @@ lint:

lint/check:
npx nx run-many --target lint

# === Testing ===

test/type:
npx nx run-many --target test:type --all

test/unit:
npx nx run-many --target test:unit --all

test/integration:
npx nx run-many --target test:integration --all

test/e2e:
npx nx run-many --target test:e2e --all

test:
make test/type
make test/unit
make test/integration
make test/e2e
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@ make docker/up
make docker/stop
```

## Testing

To run tests across all existing projects, you can use the following commands:

```bash
# Run all tests
make test

make test/type
make test/unit
make test/integration
make test/e2e
```

These commands utilize the NX CLI's run-many feature to execute the specified
targets (test or test:type) across all projects in the monorepo.

## Formatting

We use [Prettier](https://prettier.io/) and [ESLint](https://eslint.org/) to
Expand Down
1 change: 1 addition & 0 deletions apps/armory/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ armory/test/e2e/watch:
make armory/test/e2e ARGS=--watch

armory/test:
make armory/test/type
make armory/test/unit
make armory/test/integration
make armory/test/e2e
Expand Down
6 changes: 3 additions & 3 deletions apps/armory/src/__test__/fixture/price.fixture.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AssetType, Namespace, Prices, assetIdSchema } from '@narval/policy-engine-shared'
import { AssetId, AssetType, Namespace, Prices } from '@narval/policy-engine-shared'
import { sample } from 'lodash'
import { times } from 'lodash/fp'
import { z } from 'zod'
Expand All @@ -15,15 +15,15 @@ export const fiatIdSchema = z.custom<`fiat:${string}`>((value) => {

export const priceSchema = z.record(fiatIdSchema, z.number().min(0))

export const pricesSchema = z.record(assetIdSchema, priceSchema)
export const pricesSchema = z.record(AssetId, priceSchema)

const fiatIdGenerator = Generator({
schema: fiatIdSchema,
output: () => FIAT_ID_USD
})

const assetIdGenerator = Generator({
schema: assetIdSchema,
schema: AssetId,
output: () =>
sample([
...Array.from(CHAINS.values()).map(({ coin }) => coin.id),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,30 @@ import { Feed, HistoricalTransfer, JwtString } from '@narval/policy-engine-share
import { Alg, Payload, SigningAlg, hash, hexToBase64Url, privateKeyToJwk, signJwt } from '@narval/signature'
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { mapValues, omit } from 'lodash/fp'
import { omit } from 'lodash/fp'
import { privateKeyToAccount } from 'viem/accounts'
import { Config } from '../../../armory.config'
import { DataFeed } from '../../../data-feed/core/type/data-feed.type'
import { AuthorizationRequest } from '../../../orchestration/core/type/domain.type'
import { FiatId, Price } from '../../../shared/core/type/price.type'
import { Transfer } from '../../../shared/core/type/transfer-tracking.type'
import { TransferTrackingService } from '../../../transfer-tracking/core/service/transfer-tracking.service'

const buildHistoricalTranferRates = (rates: Price): Record<string, string> => {
return Object.keys(rates).reduce(
(acc, currency) => {
const price = rates[currency as FiatId]

if (price) {
acc[currency] = price.toString()
}

return acc
},
{} as Record<string, string>
)
}

@Injectable()
export class HistoricalTransferFeedService implements DataFeed<HistoricalTransfer[]> {
static SOURCE_ID = 'armory/historical-transfer-feed'
Expand Down Expand Up @@ -74,7 +90,7 @@ export class HistoricalTransferFeedService implements DataFeed<HistoricalTransfe
...omit('orgId', transfer),
amount: transfer.amount.toString(),
timestamp: transfer.createdAt.getTime(),
rates: mapValues((value) => value.toString(), transfer.rates)
rates: buildHistoricalTranferRates(transfer.rates)
}))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,12 @@ export class AuthorizationRequestService {
to: intent.to,
token: intent.token,
chainId: authzRequest.request.transactionRequest.chainId,
initiatedBy: authzRequest.authentication, // TODO: Get real initiator? -- this used to reference publicKey but should actually pull data out of a decoded JWT
// TODO: (@mattschoch) Get real initiator? -- this used to reference publicKey but
// should actually pull data out of a decoded JWT
initiatedBy: authzRequest.authentication,
createdAt: new Date(),
amount: BigInt(intent.amount),
rates: transferPrices[intent.token]
rates: transferPrices[intent.token] || {}
}

await this.transferTrackingService.track(transfer)
Expand Down
4 changes: 2 additions & 2 deletions apps/armory/src/shared/core/type/price.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ export type FiatId = `fiat:${string}`

// Not sure about using a plain number wouldn't result in precision loss with
// crypto-to-crypto rates.
export type Price = Record<FiatId, number>
export type Price = Record<FiatId, number | undefined>

export type Prices = Record<AssetId, Price>
export type Prices = Record<AssetId, Price | undefined>
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe(TransferTrackingService.name, () => {
expect(first(models)).toEqual({
...transfer,
amount: transfer.amount.toString(),
rates: mapValues((value) => value.toString(), transfer.rates)
rates: mapValues((value) => value?.toString(), transfer.rates)
})
})

Expand Down
1 change: 1 addition & 0 deletions apps/policy-engine/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ policy-engine/test/e2e/watch:
make policy-engine/test/e2e ARGS=--watch

policy-engine/test:
make policy-engine/test/type
make policy-engine/test/unit
make policy-engine/test/integration
make policy-engine/test/e2e
Expand Down
3 changes: 3 additions & 0 deletions apps/policy-engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ make policy-engine/start/dev
## Testing

```bash
# Run all tests
make policy-engine/test

make policy-engine/test/type
make policy-engine/test/unit
make policy-engine/test/integration
Expand Down
80 changes: 75 additions & 5 deletions apps/policy-engine/src/engine/__test__/e2e/evaluation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ConfigModule, ConfigService } from '@narval/config-module'
import { EncryptionModuleOptionProvider } from '@narval/encryption-module'
import { Action, Criterion, Decision, FIXTURE, Then } from '@narval/policy-engine-shared'
import { Action, Criterion, Decision, EvaluationRequest, FIXTURE, Then } from '@narval/policy-engine-shared'
import { Alg, PrivateKey, privateKeyToJwk, secp256k1PrivateKeyToJwk } from '@narval/signature'
import { HttpStatus, INestApplication } from '@nestjs/common'
import { Test, TestingModule } from '@nestjs/testing'
Expand All @@ -16,7 +16,11 @@ import { InMemoryKeyValueRepository } from '../../../shared/module/key-value/per
import { TestPrismaService } from '../../../shared/module/persistence/service/test-prisma.service'
import { getEntityStore, getPolicyStore } from '../../../shared/testing/data-store.testing'
import { getTestRawAesKeyring } from '../../../shared/testing/encryption.testing'
import { generateSignMessageRequest, generateSignTransactionRequest } from '../../../shared/testing/evaluation.testing'
import {
generateSignMessageRequest,
generateSignRawRequest,
generateSignTransactionRequest
} from '../../../shared/testing/evaluation.testing'
import { Client } from '../../../shared/type/domain.type'
import { ClientService } from '../../core/service/client.service'
import { EngineSignerConfigService } from '../../core/service/engine-signer-config.service'
Expand Down Expand Up @@ -177,9 +181,13 @@ describe('Evaluation', () => {
})

describe('when sign message ', () => {
it('evaluates a forbid', async () => {
const payload = await generateSignMessageRequest()
let payload: EvaluationRequest

beforeEach(async () => {
payload = await generateSignMessageRequest()
})

it('evaluates a forbid', async () => {
const { status, body } = await request(app.getHttpServer())
.post('/evaluations')
.set(REQUEST_HEADER_CLIENT_ID, client.clientId)
Expand Down Expand Up @@ -214,7 +222,69 @@ describe('Evaluation', () => {
)
)

const payload = await generateSignMessageRequest()
const { status, body } = await request(app.getHttpServer())
.post('/evaluations')
.set(REQUEST_HEADER_CLIENT_ID, client.clientId)
.set(REQUEST_HEADER_CLIENT_SECRET, client.clientSecret)
.send(payload)

expect(body).toMatchObject({
decision: Decision.PERMIT,
request: payload.request,
accessToken: {
value: expect.any(String)
},
approvals: {
missing: [],
required: [],
satisfied: []
}
})
expect(status).toEqual(HttpStatus.OK)
})
})

describe('when sign raw', () => {
let payload: EvaluationRequest

beforeEach(async () => {
payload = await generateSignRawRequest()
})

it('evaluates a forbid', async () => {
const { status, body } = await request(app.getHttpServer())
.post('/evaluations')
.set(REQUEST_HEADER_CLIENT_ID, client.clientId)
.set(REQUEST_HEADER_CLIENT_SECRET, client.clientSecret)
.send(payload)

expect(body).toEqual({
decision: Decision.FORBID,
request: payload.request
})
expect(status).toEqual(HttpStatus.OK)
})

it('evaluates a permit', async () => {
await clientService.savePolicyStore(
client.clientId,
await getPolicyStore(
[
{
id: 'test-permit-policy',
then: Then.PERMIT,
description: 'test permit policy',
when: [
{
criterion: Criterion.CHECK_ACTION,
args: [Action.SIGN_RAW]
}
]
}
],
privateKey
)
)

const { status, body } = await request(app.getHttpServer())
.post('/evaluations')
Expand Down
46 changes: 0 additions & 46 deletions apps/policy-engine/src/engine/evaluation-request.dto.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Body, Controller, HttpCode, HttpStatus, Post, UseGuards } from '@nestjs
import { ClientId } from '../../../../shared/decorator/client-id.decorator'
import { ClientSecretGuard } from '../../../../shared/guard/client-secret.guard'
import { EvaluationService } from '../../../core/service/evaluation.service'
import { EvaluationRequestDto } from '../../../evaluation-request.dto'
import { EvaluationRequestDto } from '../dto/evaluation-request.dto'

@Controller('/evaluations')
@UseGuards(ClientSecretGuard)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { EvaluationRequest } from '@narval/policy-engine-shared'
import { createZodDto } from 'nestjs-zod'

export class EvaluationRequestDto extends createZodDto(EvaluationRequest) {}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { toData, toInput } from './util/evaluation.util'
import { getRegoRuleTemplatePath } from './util/rego-transpiler.util'
import { build, getRegoCorePath } from './util/wasm-build.util'

const SUPPORTED_ACTIONS: Action[] = [Action.SIGN_MESSAGE, Action.SIGN_TRANSACTION]
const SUPPORTED_ACTIONS: Action[] = [Action.SIGN_MESSAGE, Action.SIGN_TRANSACTION, Action.SIGN_RAW]

export class OpenPolicyAgentEngine implements Engine<OpenPolicyAgentEngine> {
private policies: Policy[]
Expand Down
Loading

0 comments on commit d5dacde

Please sign in to comment.