Skip to content

Commit

Permalink
Support sign message
Browse files Browse the repository at this point in the history
  • Loading branch information
wcalderipe committed Apr 4, 2024
1 parent 1746c38 commit 1c09f6f
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 87 deletions.
164 changes: 114 additions & 50 deletions apps/policy-engine/src/engine/__test__/e2e/evaluation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ 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 { generateInboundEvaluationRequest } from '../../../shared/testing/evaluation.testing'
import { generateSignMessageRequest, 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 @@ -114,68 +114,132 @@ describe('Evaluation', () => {
})

describe('POST /evaluations', () => {
it('evaluates a forbid', async () => {
const payload = await generateInboundEvaluationRequest()
describe('when sign transaction', () => {
it('evaluates a forbid', async () => {
const payload = await generateSignTransactionRequest()

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)
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(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_TRANSACTION]
}
]
}
],
privateKey
)
)

const payload = await generateSignTransactionRequest()

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)
})
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_TRANSACTION]
}
]
}
],
privateKey
describe('when sign message ', () => {
it('evaluates a forbid', async () => {
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).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_MESSAGE]
}
]
}
],
privateKey
)
)
)

const payload = await generateInboundEvaluationRequest()
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)
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(body).toMatchObject({
decision: Decision.PERMIT,
request: payload.request,
accessToken: {
value: expect.any(String)
},
approvals: {
missing: [],
required: [],
satisfied: []
}
})
expect(status).toEqual(HttpStatus.OK)
})
expect(status).toEqual(HttpStatus.OK)
})

it('responds with forbid when client secret is missing', async () => {
const payload = await generateInboundEvaluationRequest()
const payload = await generateSignTransactionRequest()

const { status, body } = await request(app.getHttpServer())
.post('/evaluations')
Expand Down
6 changes: 3 additions & 3 deletions apps/policy-engine/src/engine/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FIXTURE } from '@narval/policy-engine-shared'
import { Controller, Get, Logger, Post } from '@nestjs/common'
import { generateInboundEvaluationRequest } from '../shared/testing/evaluation.testing'
import { generateSignTransactionRequest } from '../shared/testing/evaluation.testing'
import { EvaluationService } from './core/service/evaluation.service'

@Controller()
Expand All @@ -24,7 +24,7 @@ export class AppController {

@Post('/evaluation-demo')
async evaluateDemo() {
const evaluation = await generateInboundEvaluationRequest()
const evaluation = await generateSignTransactionRequest()
this.logger.log('Received evaluation', {
evaluation
})
Expand All @@ -43,6 +43,6 @@ export class AppController {

@Get('/generate-inbound-request')
generateInboundRequest() {
return generateInboundEvaluationRequest()
return generateSignTransactionRequest()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import { z } from 'zod'
import { POLICY_ENTRYPOINT } from '../open-policy-agent.constant'
import { OpenPolicyAgentException } from './exception/open-policy-agent.exception'
import { resultSchema } from './schema/open-policy-agent.schema'
import { Input, OpenPolicyAgentInstance, Result } from './type/open-policy-agent.type'
import { OpenPolicyAgentInstance, Result } from './type/open-policy-agent.type'
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]

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

Expand Down Expand Up @@ -108,7 +110,7 @@ export class OpenPolicyAgentEngine implements Engine<OpenPolicyAgentEngine> {
async evaluate(evaluation: EvaluationRequest): Promise<EvaluationResponse> {
const { action } = evaluation.request

if (action !== Action.SIGN_TRANSACTION) {
if (!SUPPORTED_ACTIONS.includes(action)) {
throw new OpenPolicyAgentException({
message: 'Open Policy Agent engine unsupported action',
suggestedHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY,
Expand Down Expand Up @@ -191,13 +193,11 @@ export class OpenPolicyAgentEngine implements Engine<OpenPolicyAgentEngine> {
})
}

const input: Input = {
...toInput(evaluation),
const input = toInput({
evaluation,
principal: credentials.principal,
// TODO: Why the EvaluationRequest specifies approvals as optional but
// the OPA input doesn't?
approvals: credentials.approvals || []
}
approvals: credentials.approvals
})

// NOTE: When we evaluate an input against the Rego policy core, it returns
// an array of results with an inner result. We perform a typecast here to
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,81 @@
import { Action, EvaluationRequest, FIXTURE, SignTransactionAction } from '@narval/policy-engine-shared'
import {
Action,
EvaluationRequest,
FIXTURE,
SignMessageAction,
SignTransactionAction
} from '@narval/policy-engine-shared'
import { InputType, decode } from '@narval/transaction-request-intent'
import { generateInboundEvaluationRequest } from '../../../../../shared/testing/evaluation.testing'
import {
generateSignMessageRequest,
generateSignTransactionRequest
} from '../../../../../shared/testing/evaluation.testing'
import { OpenPolicyAgentException } from '../../../exception/open-policy-agent.exception'
import { toData, toInput } from '../../evaluation.util'

describe('toInput', () => {
it('throws OpenPolicyAgentException when action is unsupported', () => {
const evaluation = {
const principal = FIXTURE.CREDENTIAL.Alice
const approvals = [FIXTURE.CREDENTIAL.Alice, FIXTURE.CREDENTIAL.Bob, FIXTURE.CREDENTIAL.Carol]

it('throws OpenPolicyAgentException when action is unsupported', async () => {
const evaluation = await generateSignTransactionRequest()
const unsupportedEvaluationRequest = {
...evaluation,
request: {
...evaluation.request,
action: 'UNSUPPORTED ACTION'
}
}

expect(() => toInput(evaluation as EvaluationRequest)).toThrow(OpenPolicyAgentException)
expect(() =>
toInput({ evaluation: unsupportedEvaluationRequest as EvaluationRequest, principal, approvals })
).toThrow(OpenPolicyAgentException)
expect(() =>
toInput({ evaluation: unsupportedEvaluationRequest as EvaluationRequest, principal, approvals })
).toThrow('Unsupported evaluation request action')
})

describe(`when action is ${Action.SIGN_TRANSACTION}`, () => {
let evaluation: EvaluationRequest

beforeEach(async () => {
evaluation = await generateInboundEvaluationRequest()
evaluation = await generateSignTransactionRequest()
})

it('maps the request action', () => {
const input = toInput(evaluation)
it('maps action', () => {
const input = toInput({ evaluation, principal, approvals })

expect(input.action).toEqual(evaluation.request.action)
})

it('maps the transaction request', () => {
const input = toInput(evaluation)
it('maps principal', () => {
const input = toInput({ evaluation, principal, approvals })

expect(input.principal).toEqual(principal)
})

it('maps resource', () => {
const input = toInput({ evaluation, principal, approvals })
const request = evaluation.request as SignTransactionAction

expect(input.transactionRequest).toEqual(request.transactionRequest)
expect(input.resource).toEqual({ uid: request.resourceId })
})

it('maps the transfers', () => {
const input = toInput(evaluation)
it('maps approvals', () => {
const input = toInput({ evaluation, principal, approvals })

expect(input.transfers).toEqual(evaluation.transfers)
expect(input.approvals).toEqual(approvals)
})

it('adds the transaction request intent', () => {
const input = toInput(evaluation)
it('maps transaction request', () => {
const input = toInput({ evaluation, principal, approvals })
const request = evaluation.request as SignTransactionAction

expect(input.transactionRequest).toEqual(request.transactionRequest)
})

it('adds transaction request intent', () => {
const input = toInput({ evaluation, principal, approvals })
const intent = decode({
input: {
type: InputType.TRANSACTION_REQUEST,
Expand All @@ -53,6 +86,39 @@ describe('toInput', () => {
expect(input.intent).toEqual(intent)
})
})

describe(`when action is ${Action.SIGN_MESSAGE}`, () => {
let evaluation: EvaluationRequest

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

it('maps action', () => {
const input = toInput({ evaluation, principal, approvals })

expect(input.action).toEqual(evaluation.request.action)
})

it('maps principal', () => {
const input = toInput({ evaluation, principal, approvals })

expect(input.principal).toEqual(principal)
})

it('maps resource', () => {
const input = toInput({ evaluation, principal, approvals })
const request = evaluation.request as SignMessageAction

expect(input.resource).toEqual({ uid: request.resourceId })
})

it('maps approvals', () => {
const input = toInput({ evaluation, principal, approvals })

expect(input.approvals).toEqual(approvals)
})
})
})

describe('toData', () => {
Expand Down
Loading

0 comments on commit 1c09f6f

Please sign in to comment.