Skip to content

Commit

Permalink
Rego template endpoint with DTOs (#96)
Browse files Browse the repository at this point in the history
  • Loading branch information
Samuel authored Feb 12, 2024
1 parent df6e274 commit 532c28b
Show file tree
Hide file tree
Showing 37 changed files with 1,454 additions and 559 deletions.
34 changes: 13 additions & 21 deletions apps/authz/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,21 @@ authz/test/type:
authz/test/unit:
npx nx test:unit ${AUTHZ_PROJECT_NAME} -- ${ARGS}

authz/test/unit/watch:
make authz/test/unit ARGS=--watch

authz/test/integration:
npx nx test:integration ${AUTHZ_PROJECT_NAME} -- ${ARGS}

authz/test/integration/watch:
make authz/test/integration ARGS=--watch

authz/test/e2e:
npx nx test:e2e ${AUTHZ_PROJECT_NAME} -- ${ARGS}

authz/test/e2e/watch:
make authz/test/e2e ARGS=--watch

# === Open Policy Agent & Rego ===

authz/rego/compile:
Expand All @@ -110,7 +119,9 @@ authz/rego/compile:
--output ./rego-build/policies.gz
tar -xzf ./rego-build/policies.gz -C ./rego-build/

authz/rego/wasm:
authz/rego/eval:
make authz/rego/compile

npx ts-node \
--compiler-options "{\"module\":\"CommonJS\"}" \
${AUTHZ_PROJECT_DIR}/src/opa/rego/script.ts
Expand All @@ -120,26 +131,7 @@ authz/rego/template:
ts-node -r tsconfig-paths/register \
--project ${AUTHZ_PROJECT_DIR}/tsconfig.app.json ${AUTHZ_PROJECT_DIR}/src/opa/template/script.ts

make authz/rego/compile

make authz/rego/wasm

authz/rego/bundle:
rm -rf ${AUTHZ_PROJECT_DIR}/src/opa/build

mkdir -p ${AUTHZ_PROJECT_DIR}/src/opa/build

opa build \
--bundle ${AUTHZ_PROJECT_DIR}/src/opa/rego \
--ignore "__test__" \
--output ${AUTHZ_PROJECT_DIR}/src/opa/build/policies.tar.gz

authz/rego/eval:
opa eval \
--format="pretty" \
--bundle ${AUTHZ_PROJECT_DIR}/src/opa/build/policies.tar.gz \
--input ${AUTHZ_PROJECT_DIR}/src/opa/rego/input.json \
'data.main.evaluate'
make authz/rego/eval

authz/rego/test:
opa test \
Expand Down
150 changes: 149 additions & 1 deletion apps/authz/src/app/__test__/e2e/admin.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { AccountClassification, AccountType, Action, Alg, Signature, UserRole } from '@narval/authz-shared'
import {
AccountClassification,
AccountType,
Action,
Alg,
EntityType,
Signature,
UserRole,
ValueOperators
} from '@narval/authz-shared'
import { Intents } from '@narval/transaction-request-intent'
import { HttpStatus, INestApplication } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { Test, TestingModule } from '@nestjs/testing'
import { readFileSync, unlinkSync } from 'fs'
import request from 'supertest'
import { AppModule } from '../../../app/app.module'
import { AAUser, AAUser_Credential_1 } from '../../../app/persistence/repository/mock_data'
import { PersistenceModule } from '../../../shared/module/persistence/persistence.module'
import { TestPrismaService } from '../../../shared/module/persistence/service/test-prisma.service'
import { Organization } from '../../../shared/types/entities.types'
import { Criterion, Then, TimeWindow } from '../../../shared/types/policy.type'
import { load } from '../../app.config'

const REQUEST_HEADER_ORG_ID = 'x-org-id'
Expand Down Expand Up @@ -436,4 +448,140 @@ describe('Admin Endpoints', () => {
expect(status).toEqual(HttpStatus.CREATED)
})
})

describe('POST /policies', () => {
it('sets the organization policies', async () => {
const payload = {
authentication,
approvals,
request: {
action: Action.SET_POLICY_RULES,
nonce: 'random-nonce-111',
data: [
{
then: Then.PERMIT,
name: 'examplePermitPolicy',
when: [
{
criterion: Criterion.CHECK_RESOURCE_INTEGRITY,
args: null
},
{
criterion: Criterion.CHECK_NONCE_EXISTS,
args: null
},
{
criterion: Criterion.CHECK_ACTION,
args: [Action.SIGN_TRANSACTION]
},
{
criterion: Criterion.CHECK_PRINCIPAL_ID,
args: ['[email protected]']
},
{
criterion: Criterion.CHECK_WALLET_ID,
args: ['eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b']
},
{
criterion: Criterion.CHECK_INTENT_TYPE,
args: [Intents.TRANSFER_NATIVE]
},
{
criterion: Criterion.CHECK_INTENT_TOKEN,
args: ['eip155:137/slip44:966']
},
{
criterion: Criterion.CHECK_INTENT_AMOUNT,
args: {
currency: '*',
operator: ValueOperators.LESS_THAN_OR_EQUAL,
value: '1000000000000000000'
}
},
{
criterion: Criterion.CHECK_APPROVALS,
args: [
{
approvalCount: 2,
countPrincipal: false,
approvalEntityType: EntityType.User,
entityIds: ['[email protected]', '[email protected]']
},
{
approvalCount: 1,
countPrincipal: false,
approvalEntityType: EntityType.UserRole,
entityIds: [UserRole.ADMIN]
}
]
}
]
},
{
then: Then.FORBID,
name: 'exampleForbidPolicy',
when: [
{
criterion: Criterion.CHECK_RESOURCE_INTEGRITY,
args: null
},
{
criterion: Criterion.CHECK_NONCE_EXISTS,
args: null
},
{
criterion: Criterion.CHECK_ACTION,
args: [Action.SIGN_TRANSACTION]
},
{
criterion: Criterion.CHECK_PRINCIPAL_ID,
args: ['[email protected]']
},
{
criterion: Criterion.CHECK_WALLET_ID,
args: ['eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b']
},
{
criterion: Criterion.CHECK_INTENT_TYPE,
args: [Intents.TRANSFER_NATIVE]
},
{
criterion: Criterion.CHECK_INTENT_TOKEN,
args: ['eip155:137/slip44:966']
},
{
criterion: Criterion.CHECK_SPENDING_LIMIT,
args: {
limit: '1000000000000000000',
timeWindow: {
type: TimeWindow.ROLLING,
value: 43200
},
filters: {
tokens: ['eip155:137/slip44:966'],
users: ['[email protected]']
}
}
}
]
}
]
}
}

const { status, body } = await request(app.getHttpServer())
.post('/admin/policies')
.set(REQUEST_HEADER_ORG_ID, org.uid)
.send(payload)

expect(body.policies).toMatchObject(payload.request.data)
expect(status).toEqual(HttpStatus.CREATED)

const path = `./apps/authz/src/opa/rego/generated/${body.fileId}.rego`
const rego = readFileSync(path, 'utf-8')
expect(rego).toBeDefined()

unlinkSync(path)
})
})
})
10 changes: 9 additions & 1 deletion apps/authz/src/app/core/admin.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import {
} from '@narval/authz-shared'
import { Injectable } from '@nestjs/common'
import { AddressBookAccount, Organization, Token, User, Wallet } from '../../shared/types/entities.types'
import { Policy, SetPolicyRulesRequest } from '../../shared/types/policy.type'
import { OpaService } from '../opa/opa.service'
import { AdminRepository } from '../persistence/repository/admin.repository'

@Injectable()
export class AdminService {
constructor(private adminRepository: AdminRepository) {}
constructor(private adminRepository: AdminRepository, private opaService: OpaService) {}

async createOrganization(payload: CreateOrganizationRequest): Promise<{
organization: Organization
Expand Down Expand Up @@ -98,4 +100,10 @@ export class AdminService {

return payload.request.tokens
}

async setPolicyRules(payload: SetPolicyRulesRequest): Promise<{ policies: Policy[]; fileId: string }> {
const fileId = this.opaService.generateRegoFile(payload.request.data)

return { policies: payload.request.data, fileId }
}
}
13 changes: 13 additions & 0 deletions apps/authz/src/app/http/rest/controller/admin.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
UpdateUserRequest
} from '@narval/authz-shared'
import { Body, Controller, Logger, Patch, Post } from '@nestjs/common'
import { SetPolicyRulesRequest } from '../../../../shared/types/policy.type'
import { AdminService } from '../../../core/admin.service'
import { AssignUserGroupRequestDto } from '../dto/assign-user-group-request.dto'
import { AssignUserGroupResponseDto } from '../dto/assign-user-group-response.dto'
Expand All @@ -26,6 +27,8 @@ import { CreateOrganizationRequestDto } from '../dto/create-organization-request
import { CreateOrganizationResponseDto } from '../dto/create-organization-response.dto'
import { CreateUserRequestDto } from '../dto/create-user-request.dto'
import { CreateUserResponseDto } from '../dto/create-user-response.dto'
import { SetPolicyRulesRequestDto } from '../dto/policy-rules/set-policy-rules-request.dto'
import { SetPolicyRulesResponseDto } from '../dto/policy-rules/set-policy-rules-response.dto'
import { RegisterTokensRequestDto } from '../dto/register-tokens-request.dto'
import { RegisterTokensResponseDto } from '../dto/register-tokens-response.dto'
import { RegisterWalletRequestDto } from '../dto/register-wallet-request.dto'
Expand Down Expand Up @@ -137,4 +140,14 @@ export class AdminController {
const response = new RegisterTokensResponseDto(tokens)
return response
}

@Post('/policies')
async setPolicyRules(@Body() body: SetPolicyRulesRequestDto) {
const payload: SetPolicyRulesRequest = body

const policies = await this.adminService.setPolicyRules(payload)

const response = new SetPolicyRulesResponseDto(policies)
return response
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Action } from '@narval/authz-shared'
import { ApiProperty } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import { IsArray, IsDefined, IsString, Matches, ValidateNested } from 'class-validator'
import { Policy } from '../../../../../shared/types/policy.type'
import { BaseActionDto } from '../base-action.dto'
import { BaseAdminRequestPayloadDto } from '../base-admin-request-payload.dto'

export class SetPolicyRulesDto extends BaseActionDto {
@IsDefined()
@IsString()
@Matches(Action.SET_POLICY_RULES)
@ApiProperty()
action: typeof Action.SET_POLICY_RULES

@IsDefined()
@IsArray()
@Type(() => Policy)
@ValidateNested({ each: true })
@ApiProperty()
data: Policy[]
}

export class SetPolicyRulesRequestDto extends BaseAdminRequestPayloadDto {
@IsDefined()
@ValidateNested()
@Type(() => SetPolicyRulesDto)
@ApiProperty()
request: SetPolicyRulesDto
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ApiProperty } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import { IsDefined, IsString, ValidateNested } from 'class-validator'
import { Policy } from '../../../../../shared/types/policy.type'

export class SetPolicyRulesResponseDto {
@IsDefined()
@IsString()
fileId: string

@IsDefined()
@Type(() => Policy)
@ValidateNested()
@ApiProperty({ type: () => Policy, isArray: true })
policies: Policy[]

constructor(partial: Partial<SetPolicyRulesResponseDto>) {
Object.assign(this, partial)
}
}
Loading

0 comments on commit 532c28b

Please sign in to comment.