diff --git a/apps/policy-engine/src/resource/open-policy-agent/rego/__test__/criteria/resource_test.rego b/apps/policy-engine/src/resource/open-policy-agent/rego/__test__/criteria/resource_test.rego index 9cc56d325..b7ca956f4 100644 --- a/apps/policy-engine/src/resource/open-policy-agent/rego/__test__/criteria/resource_test.rego +++ b/apps/policy-engine/src/resource/open-policy-agent/rego/__test__/criteria/resource_test.rego @@ -17,7 +17,7 @@ test_resource { groups = resource.groups with input as requestWithEip1559Transaction with data.entities as entities groups == {"test-account-group-ONE-uid"} - accountGroupsById = get.accountGroups("eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e") with input as requestWithEip1559Transaction with data.entities as entities + accountGroupsById = get.account("eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e").groups with input as requestWithEip1559Transaction with data.entities as entities accountGroupsById == {"test-account-group-ONE-uid"} checkAccountId({"eip155:eoa:0xdDcF208f219a6e6af072f2cfdc615b2c1805F98E"}) with input as requestWithEip1559Transaction with data.entities as entities diff --git a/apps/policy-engine/src/resource/open-policy-agent/rego/criteria/approval.rego b/apps/policy-engine/src/resource/open-policy-agent/rego/criteria/approval.rego index 980e5efb1..54047fe75 100644 --- a/apps/policy-engine/src/resource/open-policy-agent/rego/criteria/approval.rego +++ b/apps/policy-engine/src/resource/open-policy-agent/rego/criteria/approval.rego @@ -1,5 +1,6 @@ package main +import data.armory.entities.get import data.armory.lib.case.equalsIgnoreCase import data.armory.lib.case.findCaseInsensitive import future.keywords.in @@ -38,7 +39,7 @@ checkApproval(approval) = result { approval.approvalEntityType == "Narval::UserGroup" possibleApprovers = {user | entity = approval.entityIds[_] - users = data.entities.userGroups[lower(entity)].users + users = get.userGroups(entity).users user = users[_] } | {principal.id} @@ -50,7 +51,7 @@ checkApproval(approval) = result { approval.approvalEntityType == "Narval::UserGroup" possibleApprovers = {user | entity = approval.entityIds[_] - users = data.entities.userGroups[lower(entity)].users + users = get.userGroups(entity).users user = users[_] equalsIgnoreCase(user, principal.id) == false } @@ -63,10 +64,11 @@ checkApproval(approval) = result { checkApproval(approval) = result { approval.countPrincipal == true approval.approvalEntityType == "Narval::UserRole" - possibleApprovers = {user.id | - user = data.entities.users[_] - user.role in approval.entityIds - } | {principal.id} + possibleApprovers := {user | + role := approval.entityIds[_] + users := get.usersByRole(role) + user := users[_] + } result = getApprovalsCount(possibleApprovers) } @@ -74,10 +76,13 @@ checkApproval(approval) = result { checkApproval(approval) = result { approval.countPrincipal == false approval.approvalEntityType == "Narval::UserRole" - possibleApprovers = {user.id | - user = data.entities.users[_] - user.role in approval.entityIds - equalsIgnoreCase(user.id, principal.id) == false + role := approval.entityIds[_] + + possibleApprovers := {user | + role_id := approval.entityIds[_] + users := get.usersByRole(role_id) + user := users[_] + equalsIgnoreCase(user, principal.id) == false } result = getApprovalsCount(possibleApprovers) diff --git a/apps/policy-engine/src/resource/open-policy-agent/rego/criteria/intent/userOperation/entryPoint.rego b/apps/policy-engine/src/resource/open-policy-agent/rego/criteria/intent/userOperation/entryPoint.rego index ec5f67e49..f860ec715 100644 --- a/apps/policy-engine/src/resource/open-policy-agent/rego/criteria/intent/userOperation/entryPoint.rego +++ b/apps/policy-engine/src/resource/open-policy-agent/rego/criteria/intent/userOperation/entryPoint.rego @@ -6,6 +6,7 @@ import future.keywords.in checkEntryPointId(values) { entrypoint = getEntryPoint(input.intent) + print("entrypoint.id: ", entrypoint.id) findCaseInsensitive(entrypoint.id, values) } diff --git a/apps/policy-engine/src/resource/open-policy-agent/rego/utils/__test__/entityQueries_test.rego b/apps/policy-engine/src/resource/open-policy-agent/rego/utils/__test__/entityQueries_test.rego index 53095f409..869677971 100644 --- a/apps/policy-engine/src/resource/open-policy-agent/rego/utils/__test__/entityQueries_test.rego +++ b/apps/policy-engine/src/resource/open-policy-agent/rego/utils/__test__/entityQueries_test.rego @@ -37,21 +37,44 @@ test_account_from_address { } test_accountGroups { - groups := get.accountGroups("eip155:eoa:0xddcf208F219a6e6af072f2cfdc615b2c1805f98e") with data.entities as entities - groups == {"test-account-group-ONE-uid"} + # Test finding a group by ID + group := get.accountGroups("test-account-group-ONE-uid") with data.entities as entities + expected_group := { + "id": "test-account-group-ONE-uid", + "accounts": [ + "eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e", + "eip155:eoa:0xbbBB208f219a6e6af072f2cfdc615b2c1805f98e", + ], + "name": "dev", + } + group == expected_group # Test case insensitivity - groups_upper := get.accountGroups("EIP155:EOA:0xDDCF208F219a6e6af072f2cfdc615b2c1805f98e") with data.entities as entities - groups == groups_upper + groups_upper := get.accountGroups("test-account-group-one-uid") with data.entities as entities + group == groups_upper + + # Test non-existent input + non_existent := get.accountGroups("unknown") with data.entities as entities + non_existent == null } test_userGroups { - groups := get.userGroups("test-bob-uid") with data.entities as entities - groups == {"test-USER-group-one-uid", "test-USER-group-two-uid"} + # Test finding a group by ID + group := get.userGroups("test-USER-group-one-uid") with data.entities as entities + expected_group := { + "id": "test-USER-group-one-uid", + "name": "dev", + "users": ["test-Bob-uid", "test-Bar-uid"], + } + group == expected_group # Test case insensitivity - groups_upper := get.userGroups("TEST-bob-UID") with data.entities as entities - groups == groups_upper + groups_upper := get.userGroups("test-user-group-one-UID") with data.entities as entities + group == groups_upper + + # Test non-existent input + non_existent := get.userGroups("unknown") with data.entities as entities + non_existent == null } test_addressBookEntry { @@ -97,3 +120,16 @@ test_user { user_upper := get.user("test-BOB-uid") with data.entities as entities user == user_upper } + +test_usersByRole { + root := get.usersByRole("root") with data.entities as entities + + root == {"test-BOB-uid"} + + admin := get.usersByRole("admin") with data.entities as entities + admin == { + "test-Bar-uid", + "test-Foo-uid", + "0xAAA8ee1cbaa1856f4550c6fc24abb16c5c9b2a43", + } +} diff --git a/apps/policy-engine/src/resource/open-policy-agent/rego/utils/chainAccount.rego b/apps/policy-engine/src/resource/open-policy-agent/rego/utils/chainAccount.rego index 6cc222d74..6226687ec 100644 --- a/apps/policy-engine/src/resource/open-policy-agent/rego/utils/chainAccount.rego +++ b/apps/policy-engine/src/resource/open-policy-agent/rego/utils/chainAccount.rego @@ -143,7 +143,7 @@ intentDestinationChainAccount(intent) = destination { intentDestinationChainAccount(intent) = destination { chainAccount = parseChainAccount(intent.to) get.account(chainAccount.address) == null - addressBookData = data.entities.addressBook[lower(intent.to)] + addressBookData = get.addressBookEntry(intent.to) destination := { "id": chainAccount.id, @@ -162,6 +162,9 @@ intentDestinationChainAccount(intent) = destination { destination := mergeAccountAndAddressBook(chainAccount, accountData, addressBookData) } -getEntryPoint(intent) = data.entities.accounts[intent.entrypoint] - -getEntryPoint(intent) = data.entities.addressBook[intent.entrypoint] +getEntryPoint(intent) = entrypoint { + entrypoint := get.account(intent.entrypoint) + entrypoint != null +} else = entrypoint { + entrypoint := get.addressBookEntry(intent.entrypoint) +} diff --git a/apps/policy-engine/src/resource/open-policy-agent/rego/utils/entityInput.rego b/apps/policy-engine/src/resource/open-policy-agent/rego/utils/entityInput.rego index 7f8e5378e..a220a2998 100644 --- a/apps/policy-engine/src/resource/open-policy-agent/rego/utils/entityInput.rego +++ b/apps/policy-engine/src/resource/open-policy-agent/rego/utils/entityInput.rego @@ -7,4 +7,4 @@ resource := get.account(input.resource.uid) principal := get.user(input.principal.userId) -principalGroups = get.userGroups(input.principal.userId) +principalGroups = principal.groups diff --git a/apps/policy-engine/src/resource/open-policy-agent/rego/utils/entityQueries.rego b/apps/policy-engine/src/resource/open-policy-agent/rego/utils/entityQueries.rego index 45438ce1f..3b3388aa0 100644 --- a/apps/policy-engine/src/resource/open-policy-agent/rego/utils/entityQueries.rego +++ b/apps/policy-engine/src/resource/open-policy-agent/rego/utils/entityQueries.rego @@ -10,7 +10,6 @@ accountById(id) = account { } # Helper function to find an account by its address -# This function is case insensitive # It returns the first account found with the given address # There should be only one account with a given address accountByAddress(address) = account { @@ -20,19 +19,12 @@ accountByAddress(address) = account { }[_] } -# Helper function to prepare account data -prepareAccountData(account) = accountData { - groups := accountGroups(account.id) - accountData := object.union(account, {"groups": groups}) -} - ## Account ## Input: string -## Output: account object from data.entities.accounts | null -## -## This function doesn't assumes wether the input is an ID or an address. It just tries its best to find an account giving a string. -## - It first treats input as an ID. -## - If not found, it treats input as an address. +## Output: account object with its groups | null +## This function doesn't assumes wether the string is an ID or an address. It just tries its best to find an account giving a string. +## - It first treats string as an ID, and try to lookup at account index. +## - If not found, it treats string as an address, and try to find a matching address. ## ## 1st: It lookups the index. ## Index is created before evaluation lowercased. @@ -94,11 +86,13 @@ prepareAccountData(account) = accountData { account(string) = accountData { # First, try to find the account by ID account := accountById(string) - accountData := prepareAccountData(account) + accountGroups := groupsByAccount(account.id) + accountData := object.union(account, {"groups": accountGroups}) } else = accountData { # If not found by ID, try to find by address account := accountByAddress(string) - accountData := prepareAccountData(account) + accountGroups := groupsByAccount(account.id) + accountData := object.union(account, {"groups": accountGroups}) } else = null # If not found by ID or address, return null @@ -106,71 +100,113 @@ account(string) = accountData { ## Account Groups ## ## Input: string -## Output: set +## Output: accountGroup object | null ## -## This function returns the groups of an account. +## This function first tries to find an account group by its ID. ## ## Example entity data: ## { -## "entities": { -## "accountGroups": { -## "test-account-group-ONE-uid": { -## "id": "test-account-group-ONE-uid", -## "accounts": ["eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e"], -## "name": "dev", -## }, -## }, -## }, +## "entities": { +## "accountGroups": { +## "test-account-group-ONE-uid": { +## "id": "test-account-group-ONE-uid", +## "accounts": ["eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e"], +## "name": "dev", +## }, +## }, +## }, ## } ## -## get.accountGroups("eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e") +## get.accountGroups("test-account-group-ONE-uid") +## RETURNS { +## "id": "test-account-group-ONE-uid", +## "accounts": ["eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e"], +## "name": "dev", +## } +## +## +## get.accountGroups("unknown") +## RETURNS null +accountGroups(string) = group { + group := data.entities.accountGroups[lower(string)] +} else = null + +## Groups by Account +## +## Input: string +## Output: set of account group IDs | null +## +## This function returns a set of account group IDs that the account is a member of. +## +## get.groupsByAccount("eip155:eoa:0xddcf208f219a6e6af072f2cfdc615b2c1805f98e") ## RETURNS {"test-account-group-ONE-uid"} ## -## get.accountGroups("eip155:eoa:0x123") +## get.groupsByAccount("unknown") ## RETURNS {} -accountGroups(accountId) = groups { +groupsByAccount(accountId) = groups { groups := {group.id | group := data.entities.accountGroups[_] findCaseInsensitive(accountId, group.accounts) } -} else = {} +} else = null ## User Groups ## ## Input: string -## Output: set +## Output: userGroup object | null ## -## This function returns the groups of a user. +## This function first tries to find a user group by its ID. ## ## Example entity data: ## { -## "entities": { -## "userGroups": { -## "test-user-group-one-uid": { -## "id": "test-USER-group-one-uid", -## "name": "dev", -## "users": ["test-Bob-uid", "test-Bar-uid"], -## }, -## }, -## }, +## "entities": { +## "userGroups": { +## "test-user-group-one-uid": { +## "id": "test-USER-group-one-uid", +## "name": "dev", +## "users": ["test-Bob-uid", "test-Bar-uid"], +## }, +## }, +## }, +## } +## +## get.userGroups("test-USER-group-one-uid") +## RETURNS { +## "id": "test-USER-group-one-uid", +## "name": "dev", +## "users": ["test-Bob-uid", "test-Bar-uid"], ## } ## -## get.userGroups("test-bob-uid") +## +## get.userGroups("unknown") +## RETURNS null +userGroups(string) = group { + group := data.entities.userGroups[lower(string)] +} else = null + +## Groups by User +## +## Input: string +## Output: set of user group IDs | null +## +## This function returns a set of user group IDs that the user is a member of. +## +## get.groupsByUser("test-bob-uid") ## RETURNS {"test-USER-group-one-uid"} ## -## get.userGroups("test-foo-uid") +## get.groupsByUser("unknown") ## RETURNS {} -userGroups(userId) = groups { +groupsByUser(userId) = groups { groups := {group.id | group := data.entities.userGroups[_] findCaseInsensitive(userId, group.users) } -} else = {} +} else = null ## Address Book Entry ## ## Input: string -## Output: addressBookEntry object from data.entities.addressBook | null +## Output: addressBookEntry object | null ## ## This function returns an address book entry. ## @@ -205,7 +241,7 @@ addressBookEntry(id) = entry { ## Token ## ## Input: string -## Output: token object from data.entities.tokens | null +## Output: token object | null ## ## This function returns a token. ## @@ -242,7 +278,7 @@ token(id) = tokenData { ## User ## ## Input: string -## Output: user object from data.entities.users | null +## Output: user object with groups | null ## ## This function returns a user. ## @@ -274,6 +310,40 @@ token(id) = tokenData { ## RETURNS null user(id) = userData { user := data.entities.users[lower(id)] - groups := userGroups(user.id) + groups := groupsByUser(user.id) userData := object.union(user, {"groups": groups}) } else = null + +## User by role +## +## Input: 'admin' | 'root' | 'member' | 'manager' | 'wildcard' +## Output: set of user IDs | null +## +## This function returns a set of user IDs that have the given role. +## +## Example entity data: +## { +## "entities": { +## "users": { +## "test-bob-uid": { +## "id": "test-BOB-uid", +## "role": "root", +## }, +## "test-alice-uid": { +## "id": "test-Alice-uid", +## "role": "member", +## }, +## }, +## } +## +## get.usersByRole("root") +## RETURNS {"test-BOB-uid"} +## +## get.usersByRole("admin") +## RETURNS null +usersByRole(role) = users { + users := {user.id | + user := data.entities.users[_] + user.role == role + } +} else = null diff --git a/apps/policy-engine/src/resource/open-policy-agent/rego/utils/transfers.rego b/apps/policy-engine/src/resource/open-policy-agent/rego/utils/transfers.rego index 702c84f4a..5a509b6a6 100644 --- a/apps/policy-engine/src/resource/open-policy-agent/rego/utils/transfers.rego +++ b/apps/policy-engine/src/resource/open-policy-agent/rego/utils/transfers.rego @@ -120,7 +120,7 @@ checkTransferByUserGroups(userId, values) { checkTransferByUserGroups(userId, values) { values != wildcard - groups = get.userGroups(userId) + groups = get.user(userId).groups group := groups[_] res := findCaseInsensitive(group, values) } @@ -143,7 +143,7 @@ checkTransferByAccountGroups(chainAccountId, values) { checkTransferByAccountGroups(accountId, values) { values != wildcard - groups = get.accountGroups(accountId) + groups = get.account(accountId).groups group = groups[_] findCaseInsensitive(group, values) } diff --git a/packages/policy-engine-shared/src/lib/schema/policy.schema.ts b/packages/policy-engine-shared/src/lib/schema/policy.schema.ts index 686361cff..ffa77d8a4 100644 --- a/packages/policy-engine-shared/src/lib/schema/policy.schema.ts +++ b/packages/policy-engine-shared/src/lib/schema/policy.schema.ts @@ -39,38 +39,38 @@ export const criterionSchema = z.nativeEnum({ // Action CHECK_ACTION: 'checkAction', // Resource - CHECK_RESOURCE: 'checkResource', // TODO + CHECK_RESOURCE: 'checkResource', // Permission CHECK_PERMISSION: 'checkPermission', // Principal - CHECK_PRINCIPAL_ID: 'checkPrincipalId', // TODO + CHECK_PRINCIPAL_ID: 'checkPrincipalId', CHECK_PRINCIPAL_ROLE: 'checkPrincipalRole', - CHECK_PRINCIPAL_GROUP: 'checkPrincipalGroup', // TODO + CHECK_PRINCIPAL_GROUP: 'checkPrincipalGroup', // Resource Account - CHECK_ACCOUNT_ID: 'checkAccountId', // TODO - CHECK_ACCOUNT_ADDRESS: 'checkAccountAddress', // TODO + CHECK_ACCOUNT_ID: 'checkAccountId', + CHECK_ACCOUNT_ADDRESS: 'checkAccountAddress', CHECK_ACCOUNT_TYPE: 'checkAccountType', CHECK_ACCOUNT_CHAIN_ID: 'checkAccountChainId', - CHECK_ACCOUNT_GROUP: 'checkAccountGroup', // TODO + CHECK_ACCOUNT_GROUP: 'checkAccountGroup', CHECK_ACCOUNT_ASSIGNED: 'checkAccountAssigned', // Intent Source Account - CHECK_SOURCE_ID: 'checkSourceId', // TODO - CHECK_SOURCE_ADDRESS: 'checkSourceAddress', // TODO + CHECK_SOURCE_ID: 'checkSourceId', + CHECK_SOURCE_ADDRESS: 'checkSourceAddress', CHECK_SOURCE_ACCOUNT_TYPE: 'checkSourceAccountType', CHECK_SOURCE_CLASSIFICATION: 'checkSourceClassification', // Intent Destination Account - CHECK_DESTINATION_ID: 'checkDestinationId', // TODO - CHECK_DESTINATION_ADDRESS: 'checkDestinationAddress', // TODO + CHECK_DESTINATION_ID: 'checkDestinationId', + CHECK_DESTINATION_ADDRESS: 'checkDestinationAddress', CHECK_DESTINATION_ACCOUNT_TYPE: 'checkDestinationAccountType', CHECK_DESTINATION_CLASSIFICATION: 'checkDestinationClassification', // Intent CHECK_INTENT_TYPE: 'checkIntentType', CHECK_INTENT_CHAIN_ID: 'checkIntentChainId', CHECK_INTENT_AMOUNT: 'checkIntentAmount', - CHECK_INTENT_CONTRACT: 'checkIntentContract', // TODO - CHECK_INTENT_SPENDER: 'checkIntentSpender', // TODO - CHECK_INTENT_TOKEN: 'checkIntentToken', // TODO - CHECK_INTENT_HEX_SIGNATURE: 'checkIntentHexSignature', // TODO + CHECK_INTENT_CONTRACT: 'checkIntentContract', + CHECK_INTENT_SPENDER: 'checkIntentSpender', + CHECK_INTENT_TOKEN: 'checkIntentToken', + CHECK_INTENT_HEX_SIGNATURE: 'checkIntentHexSignature', // Intent Sign Message CHECK_INTENT_MESSAGE: 'checkIntentMessage', CHECK_INTENT_PAYLOAD: 'checkIntentPayload', @@ -78,7 +78,7 @@ export const criterionSchema = z.nativeEnum({ CHECK_INTENT_DOMAIN: 'checkIntentDomain', CHECK_INTENT_TYPED_DATA_MESSAGE: 'checkIntentTypedDataMessage', // Intent Token Transfers - CHECK_ERC1155_TOKEN_ID: 'checkErc1155TokenId', // TODO + CHECK_ERC1155_TOKEN_ID: 'checkErc1155TokenId', CHECK_ERC1155_TRANSFERS: 'checkErc1155Transfers', // Intent Permit Deadline CHECK_PERMIT_DEADLINE: 'checkPermitDeadline', diff --git a/packages/transaction-request-intent/src/lib/intent.types.ts b/packages/transaction-request-intent/src/lib/intent.types.ts index 726a726e8..6593360a8 100644 --- a/packages/transaction-request-intent/src/lib/intent.types.ts +++ b/packages/transaction-request-intent/src/lib/intent.types.ts @@ -125,17 +125,6 @@ export type UserOperation = { export type TypedDataIntent = SignTypedData | Permit | Permit2 -// from -// to -// owner -// spender -// token -// entrypoint -// bytecode -// contract -// hexSignature - - export type Intent = | TransferNative | TransferErc20