Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ocean-node",
"version": "3.2.2",
"version": "3.2.21",
"description": "Ocean Node is used to run all core services in the Ocean stack",
"author": "Ocean Protocol Foundation",
"license": "Apache-2.0",
Expand Down
2 changes: 2 additions & 0 deletions src/@types/OceanNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export interface OceanNodeConfig {
httpCertPath?: string
httpKeyPath?: string
enableBenchmark?: boolean
isPSConfigured: boolean
persistentStorage?: PersistentStorageConfig
}

Expand Down Expand Up @@ -185,6 +186,7 @@ export interface OceanNodeStatus {
version: string
http: boolean
p2p: boolean
isPSConfigured: boolean
provider: OceanNodeProvider[]
indexer: OceanNodeIndexer[]
escrowAddress: AddressPerChain
Expand Down
2 changes: 2 additions & 0 deletions src/OceanNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { createPersistentStorage } from './components/persistentStorage/createPe
import { PersistentStorageFactory } from './components/persistentStorage/PersistentStorageFactory.js'
import { isAddress, FallbackProvider, ethers } from 'ethers'
import { create256Hash } from './utils/crypt.js'
import { isPolicyServerConfigured } from './utils/config.js'

export interface RequestLimiter {
requester: string | string[] // IP address or peer ID
Expand Down Expand Up @@ -255,6 +256,7 @@ export class OceanNode {
}

public getConfig(): OceanNodeConfig {
this.config.isPSConfigured = isPolicyServerConfigured()
return this.config
}

Expand Down
15 changes: 15 additions & 0 deletions src/components/core/handler/downloadHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ export class DownloadHandler extends CommandHandler {
if (credentials) {
// if POLICY_SERVER_URL exists, then ocean-node will NOT perform any checks.
// It will just use the existing code and let PolicyServer decide.
CORE_LOGGER.logMessage('Checking DDO level credentials', true)
CORE_LOGGER.logMessage(
'isPolicyServerConfigured: ' + isPolicyServerConfigured(),
true
)
if (isPolicyServerConfigured()) {
const response = await policyServer.checkDownload(
ddoInstance.getDid(),
Expand All @@ -321,14 +326,24 @@ export class DownloadHandler extends CommandHandler {
task.consumerAddress,
task.policyServer
)
CORE_LOGGER.logMessage(
'Policy server response: ' + JSON.stringify(response),
true
)
accessGrantedDDOLevel = response.success
} else {
CORE_LOGGER.logMessage('Checking DDO level credentials INTERNAL', true)
CORE_LOGGER.logMessage(
'isPolicyServerConfigured: ' + isPolicyServerConfigured(),
true
)
accessGrantedDDOLevel = await checkCredentials(
task.consumerAddress,
credentials as Credentials,
await blockchain.getSigner()
)
}
CORE_LOGGER.logMessage('DDO level credentials check complete', true)
if (!accessGrantedDDOLevel) {
CORE_LOGGER.logMessage(
`Error: Access to asset ${ddoInstance.getDid()} was denied`,
Expand Down
159 changes: 156 additions & 3 deletions src/components/core/handler/feesHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import { ProviderInitialize } from '../../../@types/Fees.js'
import { getNonce } from '../utils/nonceHandler.js'
import { streamToString } from '../../../utils/util.js'
import { isOrderingAllowedForAsset } from './downloadHandler.js'
import { DDOManager } from '@oceanprotocol/ddo-js'
import { Credentials, DDOManager } from '@oceanprotocol/ddo-js'
import { checkCredentials } from '../../../utils/credentials.js'
import { isPolicyServerConfigured } from '../../../utils/index.js'
import { PolicyServer } from '../../policyServer/index.js'

export class FeesHandler extends CommandHandler {
validate(command: GetFeesCommand): ValidateParams {
Expand Down Expand Up @@ -60,14 +63,164 @@ export class FeesHandler extends CommandHandler {
}
}
const ddoInstance = DDOManager.getDDOClass(ddo)
const { services } = ddoInstance.getDDOFields() as any
const {
chainId: ddoChainId,
credentials,
services
} = ddoInstance.getDDOFields() as any
const service = services.find((what: any) => what.id === task.serviceId)
if (!service) {
errorMsg = 'Invalid serviceId'
}
if (service.type === 'compute') {
if (service?.type === 'compute') {
errorMsg = 'Use the initializeCompute endpoint to initialize compute jobs'
}

if (errorMsg) {
PROVIDER_LOGGER.logMessageWithEmoji(
errorMsg,
true,
GENERIC_EMOJIS.EMOJI_CROSS_MARK,
LOG_LEVELS_STR.LEVEL_ERROR
)
return {
stream: null,
status: {
httpStatus: 500,
error: errorMsg
}
}
}

const policyServer = new PolicyServer()
if (credentials || service.credentials) {
if (!task.consumerAddress) {
return {
stream: null,
status: {
httpStatus: 400,
error: 'consumerAddress is required to check credentials'
}
}
}

let signer
if (!isPolicyServerConfigured()) {
const oceanNode = this.getOceanNode()
const supportedNetwork = oceanNode.getConfig().supportedNetworks?.[ddoChainId]
if (!supportedNetwork) {
return {
stream: null,
status: {
httpStatus: 400,
error: `Fees handler: Unsupported chain id ${ddoChainId}`
}
}
}

const blockchain = oceanNode.getBlockchain(supportedNetwork.chainId)
if (!blockchain) {
return {
stream: null,
status: {
httpStatus: 400,
error: `Fees handler: Blockchain instance not available for chain ${supportedNetwork.chainId}`
}
}
}

const { ready, error } = await blockchain.isNetworkReady()
if (!ready) {
return {
stream: null,
status: {
httpStatus: 400,
error: `Fees handler: ${error}`
}
}
}
signer = await blockchain.getSigner()
}

let accessGrantedDDOLevel = false
PROVIDER_LOGGER.info(
`Checking credentials for consumer address: ${task.consumerAddress} AND credentials: ${JSON.stringify(credentials)}`
)
if (credentials) {
if (isPolicyServerConfigured()) {
PROVIDER_LOGGER.info(
`Checking credentials for consumer address on policy server: ${task.consumerAddress}`
)
const response = await policyServer.initializePSVerification(
ddoInstance.getDid(),
ddo,
task.serviceId,
task.consumerAddress,
task.policyServer
)
accessGrantedDDOLevel = response.success
} else {
PROVIDER_LOGGER.info(
`Checking credentials for consumer address internal: ${task.consumerAddress}`
)
accessGrantedDDOLevel = await checkCredentials(
task.consumerAddress,
credentials as Credentials,
signer
)
}
}

PROVIDER_LOGGER.info(`credentials: ${JSON.stringify(credentials)}`)
PROVIDER_LOGGER.info(`accessGrantedDDOLevel: ${accessGrantedDDOLevel}`)

if (credentials && !accessGrantedDDOLevel) {
const error = `Error: Access to asset ${ddoInstance.getDid()} was denied`
PROVIDER_LOGGER.error(error)
return {
stream: null,
status: { httpStatus: 403, error }
}
}

if (service.credentials) {
let accessGrantedServiceLevel: boolean
if (isPolicyServerConfigured()) {
const response = await policyServer.initializePSVerification(
ddoInstance.getDid(),
ddo,
service.id,
task.consumerAddress,
task.policyServer
)
accessGrantedServiceLevel = accessGrantedDDOLevel || response.success
} else {
PROVIDER_LOGGER.info(
'Checking credentials for consumer address internal for service: ' +
task.consumerAddress
)
accessGrantedServiceLevel = await checkCredentials(
task.consumerAddress,
service.credentials,
signer
)
}
PROVIDER_LOGGER.info(
`service.credentials: ${JSON.stringify(service.credentials)}`
)
PROVIDER_LOGGER.info(`accessGrantedServiceLevel: ${accessGrantedServiceLevel}`)

if (!accessGrantedServiceLevel) {
const error = `Error: Access to service with id ${service.id} was denied`
PROVIDER_LOGGER.error(error)
return {
stream: null,
status: { httpStatus: 403, error }
}
}
}
}

const now = new Date().getTime() / 1000
let validUntil = service.timeout === 0 ? 0 : now + service.timeout // first, make it service default
if (task.validUntil && !isNaN(task.validUntil)) {
Expand Down
9 changes: 8 additions & 1 deletion src/components/core/utils/nonceHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,23 @@ async function validateNonceAndSignature(
['bytes'],
[ethers.hexlify(ethers.toUtf8Bytes(message))]
)
const messageHashBytes = ethers.toBeArray(consumerMessage)
const messageHashBytes = ethers.getBytes(consumerMessage)
const legacyMessageHashBytes = ethers.toBeArray(consumerMessage)

// Try EOA signature validation
try {
const addressFromHashSignature = ethers.verifyMessage(consumerMessage, signature)
const addressFromBytesSignature = ethers.verifyMessage(messageHashBytes, signature)
const addressFromLegacyBytesSignature = ethers.verifyMessage(
legacyMessageHashBytes,
signature
)
if (
ethers.getAddress(addressFromHashSignature)?.toLowerCase() ===
ethers.getAddress(consumer)?.toLowerCase() ||
ethers.getAddress(addressFromBytesSignature)?.toLowerCase() ===
ethers.getAddress(consumer)?.toLowerCase() ||
ethers.getAddress(addressFromLegacyBytesSignature)?.toLowerCase() ===
ethers.getAddress(consumer)?.toLowerCase()
) {
return { valid: true }
Expand Down
2 changes: 2 additions & 0 deletions src/components/core/utils/statusHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export async function status(
version: getPackageVersion(),
http: config.hasHttp,
p2p: config.hasP2P,
isPSConfigured: config.isPSConfigured,
provider: [],
indexer: [],
escrowAddress: {},
Expand All @@ -152,6 +153,7 @@ export async function status(
nodeStatus.platform.freemem = os.freemem()
nodeStatus.platform.loadavg = os.loadavg()
nodeStatus.uptime = process.uptime()
nodeStatus.isPSConfigured = config.isPSConfigured

// depends on request
if (detailed) {
Expand Down
24 changes: 22 additions & 2 deletions src/test/integration/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from '../../utils/index.js'
import { DownloadHandler } from '../../components/core/handler/downloadHandler.js'
import { GetDdoHandler } from '../../components/core/handler/ddoHandler.js'
import { ProviderFees } from '../../components/core/utils/feesHandler.js'

import { Readable } from 'stream'
import { OceanNodeConfig } from '../../@types/OceanNode.js'
Expand Down Expand Up @@ -306,28 +307,47 @@ describe('********** [Credentials Flow] - Should run a complete node flo
it('should start an order for all consumers', async function () {
this.timeout(DEFAULT_TEST_TIMEOUT * 3)

const providerFeesFactory = new ProviderFees(oceanNode)
const createTestProviderFees = async (asset: any) => {
const service = asset.services[0]
const now = Date.now() / 1000
const validUntil = service.timeout === 0 ? 0 : now + service.timeout
const providerFees = await providerFeesFactory.createProviderFee(
asset,
service,
validUntil
)
assert(providerFees, 'provider fees could not be created')
return providerFees
}

const providerFees = await createTestProviderFees(ddo)
for (let i = 0; i < 3; i++) {
const orderTxReceipt = await orderAsset(
ddo,
0,
consumerAccounts[i],
consumerAddresses[i],
publisherAccount,
oceanNode
oceanNode,
providerFees
)
assert(orderTxReceipt, `order transaction for consumer ${i} failed`)
const txHash = orderTxReceipt.hash
assert(txHash, `transaction id not found for consumer ${i}`)
orderTxIds.push(txHash)
}

const providerFeesWithMatchAll = await createTestProviderFees(ddoWithMatchAll)
for (let i = 0; i < 3; i++) {
const orderTxReceipt = await orderAsset(
ddoWithMatchAll,
0,
consumerAccounts[i],
consumerAddresses[i],
publisherAccount,
oceanNode
oceanNode,
providerFeesWithMatchAll
)
assert(orderTxReceipt, `order transaction for consumer ${i} failed`)
const txHash = orderTxReceipt.hash
Expand Down
2 changes: 2 additions & 0 deletions src/utils/config/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { CONFIG_LOGGER } from '../logging/common.js'
import { LOG_LEVELS_STR, GENERIC_EMOJIS } from '../logging/Logger.js'
import { OceanNodeConfigSchema } from './schemas.js'
import { ENV_TO_CONFIG_MAPPING } from './constants.js'
import { isPolicyServerConfigured } from '../config.js'
import { fileURLToPath } from 'url'
import lodash from 'lodash'

Expand Down Expand Up @@ -281,6 +282,7 @@ export function buildMergedConfig(): OceanNodeConfig {
config.c2dClusters = buildC2DClusters(
config.dockerComputeEnvironments as C2DDockerConfig[]
)
config.isPSConfigured = isPolicyServerConfigured()

return config as OceanNodeConfig
}
Expand Down
Loading
Loading