Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Marko Milić 2025-12-17 15:23:31 +01:00
commit 0db2af0bd2
13 changed files with 1495 additions and 2181 deletions

30
flushall_build_and_run.sh Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash
# Script to flush Redis, build the project, and run Jetty
#
# This script should be run from the OBP-API root directory:
# cd /path/to/OBP-API
# ./flushall_build_and_run.sh
set -e # Exit on error
echo "=========================================="
echo "Flushing Redis cache..."
echo "=========================================="
redis-cli <<EOF
flushall
exit
EOF
if [ $? -eq 0 ]; then
echo "Redis cache flushed successfully"
else
echo "Warning: Failed to flush Redis cache. Continuing anyway..."
fi
echo ""
echo "=========================================="
echo "Building and running with Maven..."
echo "=========================================="
export MAVEN_OPTS="-Xss128m"
mvn install -pl .,obp-commons && mvn jetty:run -pl obp-api

View File

@ -1,856 +0,0 @@
# ABAC Rule Object Properties Reference
This document provides a comprehensive reference for all properties available on objects that can be used in ABAC (Attribute-Based Access Control) rules.
## Overview
When you write ABAC rules, you have access to eleven objects:
1. **authenticatedUser** - The authenticated user making the API call (always available)
2. **authenticatedUserAttributes** - Non-personal attributes for the authenticated user (always available)
3. **authenticatedUserAuthContext** - Auth context for the authenticated user (always available)
4. **onBehalfOfUserOpt** - Optional user for delegation scenarios
5. **onBehalfOfUserAttributes** - Non-personal attributes for the onBehalfOf user (always available, may be empty)
6. **onBehalfOfUserAuthContext** - Auth context for the onBehalfOf user (always available, may be empty)
7. **user** - A user object (always available)
8. **bankOpt** - Optional bank context
9. **accountOpt** - Optional account context
10. **transactionOpt** - Optional transaction context
11. **customerOpt** - Optional customer context
**Important: All objects are READ-ONLY.** You cannot modify user attributes, auth context, or any other objects within ABAC rules.
## How to Use This Reference
When writing ABAC rules, you can access properties using dot notation:
```scala
// Example: Check if authenticated user is admin
authenticatedUser.emailAddress.endsWith("@admin.com")
// Example: Check authenticated user attributes
authenticatedUserAttributes.exists(attr => attr.name == "department" && attr.value == "finance")
// Example: Check authenticated user auth context
authenticatedUserAuthContext.exists(ctx => ctx.key == "session_id")
// Example: Check if delegation is present
onBehalfOfUserOpt.isDefined
// Example: Check onBehalfOf user attributes
onBehalfOfUserAttributes.exists(attr => attr.name == "role" && attr.value == "manager")
// Example: Check onBehalfOf user auth context
onBehalfOfUserAuthContext.exists(ctx => ctx.key == "device_id")
// Example: Check if user has specific email
user.emailAddress == "alice@example.com"
// Example: Check if account balance is above 1000
accountOpt.exists(account => account.balance.toDouble > 1000.0)
// Example: Check if bank is in UK
bankOpt.exists(bank => bank.bankId.value.startsWith("gh."))
```
---
## 1. authenticatedUser (User)
The authenticated user making the API call. This is always available (not optional).
### Available Properties
| Property | Type | Description | Example |
|----------|------|-------------|---------|
| `userId` | `String` | Unique UUID for the user | `"f47ac10b-58cc-4372-a567-0e02b2c3d479"` |
| `idGivenByProvider` | `String` | Same as username | `"alice@example.com"` |
| `provider` | `String` | Authentication provider | `"obp"`, `"oauth"`, `"openid"` |
| `emailAddress` | `String` | User's email address | `"alice@example.com"` |
| `name` | `String` | User's full name | `"Alice Smith"` |
| `createdByConsentId` | `Option[String]` | Consent ID if user created via consent | `Some("consent-123")` or `None` |
| `createdByUserInvitationId` | `Option[String]` | User invitation ID if applicable | `Some("invite-456")` or `None` |
| `isDeleted` | `Option[Boolean]` | Whether user is deleted | `Some(false)` or `None` |
| `lastMarketingAgreementSignedDate` | `Option[Date]` | Last marketing agreement date | `Some(Date)` or `None` |
| `lastUsedLocale` | `Option[String]` | Last used locale/language | `Some("en_GB")` or `None` |
### Helper Methods
| Method | Type | Description |
|--------|------|-------------|
| `isOriginalUser` | `Boolean` | True if user created by OBP (not via consent) |
| `isConsentUser` | `Boolean` | True if user created via consent |
### Example Rules Using authenticatedUser
```scala
// 1. Allow only admin users (by email suffix)
authenticatedUser.emailAddress.endsWith("@admin.com")
// 2. Allow specific user by ID
authenticatedUser.userId == "f47ac10b-58cc-4372-a567-0e02b2c3d479"
// 3. Allow only original users (not consent users)
authenticatedUser.isOriginalUser
// 4. Check if user has name
authenticatedUser.name.nonEmpty
// 5. Check authentication provider
authenticatedUser.provider == "obp"
// 6. Complex condition
authenticatedUser.emailAddress.endsWith("@admin.com") ||
authenticatedUser.name.contains("Manager")
```
---
## 2. authenticatedUserAttributes (List[UserAttribute])
Non-personal attributes for the authenticated user. This is always available (not optional), but may be an empty list.
### UserAttribute Properties
| Property | Type | Description | Example |
|----------|------|-------------|---------|
| `userAttributeId` | `String` | Unique attribute ID | `"attr-123"` |
| `userId` | `String` | User ID this attribute belongs to | `"user-456"` |
| `name` | `String` | Attribute name | `"department"`, `"role"`, `"clearance_level"` |
| `attributeType` | `UserAttributeType.Value` | Type of attribute | `UserAttributeType.STRING`, `UserAttributeType.INTEGER` |
| `value` | `String` | Attribute value | `"finance"`, `"manager"`, `"5"` |
| `insertDate` | `Date` | When attribute was created | `Date(...)` |
| `isPersonal` | `Boolean` | Whether attribute is personal (always false here) | `false` |
### Example Rules Using authenticatedUserAttributes
```scala
// 1. Check if user has a specific attribute
authenticatedUserAttributes.exists(attr =>
attr.name == "department" && attr.value == "finance"
)
// 2. Check if user has clearance level >= 3
authenticatedUserAttributes.exists(attr =>
attr.name == "clearance_level" &&
attr.value.toIntOption.exists(_ >= 3)
)
// 3. Check if user has any attributes
authenticatedUserAttributes.nonEmpty
// 4. Check multiple attributes (AND)
val hasDepartment = authenticatedUserAttributes.exists(_.name == "department")
val hasRole = authenticatedUserAttributes.exists(_.name == "role")
hasDepartment && hasRole
// 5. Get specific attribute value
val departmentOpt = authenticatedUserAttributes.find(_.name == "department").map(_.value)
departmentOpt.contains("finance")
// 6. Check attribute with multiple possible values (OR)
authenticatedUserAttributes.exists(attr =>
attr.name == "role" &&
List("admin", "manager", "supervisor").contains(attr.value)
)
// 7. Combine with user properties
authenticatedUser.emailAddress.endsWith("@admin.com") ||
authenticatedUserAttributes.exists(attr => attr.name == "admin_override" && attr.value == "true")
```
---
## 3. authenticatedUserAuthContext (List[UserAuthContext])
Authentication context for the authenticated user. This is always available (not optional), but may be an empty list.
**READ-ONLY:** These values cannot be modified within ABAC rules.
### UserAuthContext Properties
| Property | Type | Description | Example |
|----------|------|-------------|---------|
| `userAuthContextId` | `String` | Unique auth context ID | `"ctx-123"` |
| `userId` | `String` | User ID this context belongs to | `"user-456"` |
| `key` | `String` | Context key | `"session_id"`, `"ip_address"`, `"device_id"` |
| `value` | `String` | Context value | `"sess-abc-123"`, `"192.168.1.1"`, `"device-xyz"` |
| `timeStamp` | `Date` | When context was created | `Date(...)` |
| `consumerId` | `String` | Consumer/app that created this context | `"consumer-789"` |
### Example Rules Using authenticatedUserAuthContext
```scala
// 1. Check if user has a specific auth context
authenticatedUserAuthContext.exists(ctx =>
ctx.key == "ip_address" && ctx.value.startsWith("192.168.")
)
// 2. Check if session exists
authenticatedUserAuthContext.exists(ctx => ctx.key == "session_id")
// 3. Check if auth context was recently created (within last hour)
import java.time.Instant
import java.time.temporal.ChronoUnit
authenticatedUserAuthContext.exists(ctx => {
val now = Instant.now()
val ctxInstant = ctx.timeStamp.toInstant
ChronoUnit.HOURS.between(ctxInstant, now) < 1
})
// 4. Check multiple context values (AND)
val hasSession = authenticatedUserAuthContext.exists(_.key == "session_id")
val hasDevice = authenticatedUserAuthContext.exists(_.key == "device_id")
hasSession && hasDevice
// 5. Get specific context value
val ipAddressOpt = authenticatedUserAuthContext.find(_.key == "ip_address").map(_.value)
ipAddressOpt.exists(ip => ip.startsWith("10.0."))
// 6. Check consumer ID
authenticatedUserAuthContext.exists(ctx =>
ctx.consumerId == "trusted-consumer-123"
)
// 7. Combine with user properties
authenticatedUser.emailAddress.endsWith("@admin.com") &&
authenticatedUserAuthContext.exists(_.key == "mfa_verified" && _.value == "true")
```
---
## 4. onBehalfOfUserOpt (Option[User])
Optional user for delegation scenarios. Present when someone acts on behalf of another user.
This is an `Option[User]` - use `.exists()`, `.isDefined`, `.isEmpty`, or pattern matching.
### Available Properties (when present)
Same properties as `authenticatedUser` (see section 1 above).
**Note:** When `onBehalfOfUserOpt` is present, the corresponding `onBehalfOfUserAttributes` and `onBehalfOfUserAuthContext` lists will contain data for that user.
### Example Rules Using onBehalfOfUserOpt
```scala
// 1. Check if delegation is being used
onBehalfOfUserOpt.isDefined
// 2. Check if no delegation (direct access only)
onBehalfOfUserOpt.isEmpty
// 3. Check delegation user's email
onBehalfOfUserOpt.exists(delegatedUser =>
delegatedUser.emailAddress.endsWith("@company.com")
)
// 4. Allow if authenticated user is customer service AND delegation is used
val isCustomerService = authenticatedUser.emailAddress.contains("@customerservice.com")
val hasDelegation = onBehalfOfUserOpt.isDefined
isCustomerService && hasDelegation
// 5. Check both authenticated and delegation users
onBehalfOfUserOpt match {
case Some(delegatedUser) =>
authenticatedUser.emailAddress.endsWith("@admin.com") &&
delegatedUser.emailAddress.nonEmpty
case None => true // No delegation, allow
}
```
---
## 5. onBehalfOfUserAttributes (List[UserAttribute])
Non-personal attributes for the onBehalfOf user. This is always available (not optional), but will be an empty list if no delegation is happening.
**READ-ONLY:** These values cannot be modified within ABAC rules.
### UserAttribute Properties
Same properties as `authenticatedUserAttributes` (see section 2 above).
### Example Rules Using onBehalfOfUserAttributes
```scala
// 1. Check if onBehalfOf user has specific attribute
onBehalfOfUserAttributes.exists(attr =>
attr.name == "department" && attr.value == "sales"
)
// 2. Check if onBehalfOf user has attributes (delegation with data)
onBehalfOfUserAttributes.nonEmpty
// 3. Verify delegation user has required role
onBehalfOfUserOpt.isDefined &&
onBehalfOfUserAttributes.exists(attr =>
attr.name == "role" && attr.value == "manager"
)
// 4. Compare authenticated and onBehalfOf user departments
val authDept = authenticatedUserAttributes.find(_.name == "department").map(_.value)
val onBehalfDept = onBehalfOfUserAttributes.find(_.name == "department").map(_.value)
authDept == onBehalfDept
// 5. Check clearance level for delegation
onBehalfOfUserAttributes.exists(attr =>
attr.name == "clearance_level" &&
attr.value.toIntOption.exists(_ >= 2)
)
```
---
## 6. onBehalfOfUserAuthContext (List[UserAuthContext])
Authentication context for the onBehalfOf user. This is always available (not optional), but will be an empty list if no delegation is happening.
**READ-ONLY:** These values cannot be modified within ABAC rules.
### UserAuthContext Properties
Same properties as `authenticatedUserAuthContext` (see section 3 above).
### Example Rules Using onBehalfOfUserAuthContext
```scala
// 1. Check if onBehalfOf user has active session
onBehalfOfUserAuthContext.exists(ctx => ctx.key == "session_id")
// 2. Verify onBehalfOf user IP is from internal network
onBehalfOfUserAuthContext.exists(ctx =>
ctx.key == "ip_address" && ctx.value.startsWith("10.0.")
)
// 3. Check if both authenticated and onBehalfOf users have MFA
val authHasMFA = authenticatedUserAuthContext.exists(_.key == "mfa_verified")
val onBehalfHasMFA = onBehalfOfUserAuthContext.exists(_.key == "mfa_verified")
authHasMFA && onBehalfHasMFA
// 4. Verify delegation has auth context
onBehalfOfUserOpt.isDefined && onBehalfOfUserAuthContext.nonEmpty
// 5. Check consumer for delegation
onBehalfOfUserAuthContext.exists(ctx =>
ctx.consumerId == "trusted-consumer-123"
)
```
---
## 7. user (User)
A user object. This is always available (not optional).
### Available Properties
Same properties as `authenticatedUser` (see section 1 above).
### Example Rules Using user
```scala
// 1. Check user email
user.emailAddress == "alice@example.com"
// 2. Check user by ID
user.userId == "f47ac10b-58cc-4372-a567-0e02b2c3d479"
// 3. Check user provider
user.provider == "obp"
// 4. Compare with authenticated user
user.userId == authenticatedUser.userId
// 5. Check if user owns account (if ownership data available)
accountOpt.exists(account =>
account.owners.exists(owner => owner.userId == user.userId)
)
```
---
## 8. bankOpt (Option[Bank])
Optional bank context. Present when `bank_id` is provided in the API request.
### Available Properties (when present)
| Property | Type | Description | Example |
|----------|------|-------------|---------|
| `bankId` | `BankId` | Unique bank identifier | `BankId("gh.29.uk")` |
| `shortName` | `String` | Short name of bank | `"GH Bank"` |
| `fullName` | `String` | Full legal name | `"Great Britain Bank Ltd"` |
| `logoUrl` | `String` | URL to bank logo | `"https://example.com/logo.png"` |
| `websiteUrl` | `String` | Bank website URL | `"https://www.ghbank.co.uk"` |
| `bankRoutingScheme` | `String` | Routing scheme | `"SWIFT_BIC"`, `"UK.SORTCODE"` |
| `bankRoutingAddress` | `String` | Routing address/code | `"GHBKGB2L"` |
| `swiftBic` | `String` | SWIFT BIC code (deprecated) | `"GHBKGB2L"` |
| `nationalIdentifier` | `String` | National identifier (deprecated) | `"123456"` |
### Accessing BankId Value
```scala
// Get the string value from BankId
bankOpt.exists(bank => bank.bankId.value == "gh.29.uk")
```
### Example Rules Using bankOpt
```scala
// 1. Allow only UK banks (by ID prefix)
bankOpt.exists(bank =>
bank.bankId.value.startsWith("gh.") ||
bank.bankId.value.startsWith("uk.")
)
// 2. Allow specific bank
bankOpt.exists(bank => bank.bankId.value == "gh.29.uk")
// 3. Check bank name
bankOpt.exists(bank => bank.shortName.contains("GH"))
// 4. Check SWIFT BIC
bankOpt.exists(bank => bank.swiftBic.startsWith("GHBK"))
// 5. Allow if no bank context provided
bankOpt.isEmpty
// 6. Check website URL
bankOpt.exists(bank => bank.websiteUrl.contains(".uk"))
```
---
## 9. accountOpt (Option[BankAccount])
Optional bank account context. Present when `account_id` is provided in the API request.
### Available Properties (when present)
| Property | Type | Description | Example |
|----------|------|-------------|---------|
| `accountId` | `AccountId` | Unique account identifier | `AccountId("8ca8a7e4-6d02-48e3...")` |
| `accountType` | `String` | Type of account | `"CURRENT"`, `"SAVINGS"`, `"330"` |
| `balance` | `BigDecimal` | Current account balance | `1234.56` |
| `currency` | `String` | Currency code (ISO 4217) | `"GBP"`, `"EUR"`, `"USD"` |
| `name` | `String` | Account name | `"Main Checking Account"` |
| `label` | `String` | Account label | `"Personal Account"` |
| `number` | `String` | Account number | `"12345678"` |
| `bankId` | `BankId` | Bank identifier | `BankId("gh.29.uk")` |
| `lastUpdate` | `Date` | Last transaction refresh date | `Date(...)` |
| `branchId` | `String` | Branch identifier | `"branch-123"` |
| `accountRoutings` | `List[AccountRouting]` | Account routing information | `List(AccountRouting(...))` |
| `accountRules` | `List[AccountRule]` | Account rules (optional) | `List(...)` |
| `accountHolder` | `String` | Account holder name (deprecated) | `"Alice Smith"` |
| `attributes` | `Option[List[Attribute]]` | Account attributes | `Some(List(...))` or `None` |
### Important Notes
- `balance` is a `BigDecimal` - convert to `Double` if needed: `account.balance.toDouble`
- `accountId.value` gives the string value
- `bankId.value` gives the bank ID string
- Use `accountOpt.exists()` to safely check properties
### Example Rules Using accountOpt
```scala
// 1. Check minimum balance
accountOpt.exists(account => account.balance.toDouble >= 1000.0)
// 2. Check account currency
accountOpt.exists(account => account.currency == "GBP")
// 3. Check account type
accountOpt.exists(account => account.accountType == "CURRENT")
// 4. Check account belongs to specific bank
accountOpt.exists(account => account.bankId.value == "gh.29.uk")
// 5. Check account number
accountOpt.exists(account => account.number.startsWith("123"))
// 6. Check if account has label
accountOpt.exists(account => account.label.nonEmpty)
// 7. Complex balance and currency check
accountOpt.exists(account =>
account.balance.toDouble > 5000.0 &&
account.currency == "GBP"
)
// 8. Check account attributes (if available)
accountOpt.exists(account =>
account.attributes.exists(attrs =>
attrs.exists(attr => attr.name == "accountStatus" && attr.value == "active")
)
)
```
---
## 10. transactionOpt (Option[Transaction])
Optional transaction context. Present when `transaction_id` is provided in the API request.
Uses the `TransactionCore` type.
### Available Properties (when present)
| Property | Type | Description | Example |
|----------|------|-------------|---------|
| `id` | `TransactionId` | Unique transaction identifier | `TransactionId("trans-123")` |
| `thisAccount` | `BankAccount` | The account this transaction belongs to | `BankAccount(...)` |
| `otherAccount` | `CounterpartyCore` | The counterparty account | `CounterpartyCore(...)` |
| `transactionType` | `String` | Type of transaction | `"DEBIT"`, `"CREDIT"` |
| `amount` | `BigDecimal` | Transaction amount | `250.00` |
| `currency` | `String` | Currency code | `"GBP"`, `"EUR"`, `"USD"` |
| `description` | `Option[String]` | Transaction description | `Some("Payment to supplier")` or `None` |
| `startDate` | `Date` | Transaction start date | `Date(...)` |
| `finishDate` | `Date` | Transaction completion date | `Date(...)` |
| `balance` | `BigDecimal` | Account balance after transaction | `1234.56` |
### Example Rules Using transactionOpt
```scala
// 1. Allow transactions under a limit
transactionOpt.exists(txn => txn.amount.toDouble < 10000.0)
// 2. Check transaction type
transactionOpt.exists(txn => txn.transactionType == "CREDIT")
// 3. Check transaction currency
transactionOpt.exists(txn => txn.currency == "GBP")
// 4. Check transaction description
transactionOpt.exists(txn =>
txn.description.exists(desc => desc.contains("salary"))
)
// 5. Check transaction belongs to account
(transactionOpt, accountOpt) match {
case (Some(txn), Some(account)) =>
txn.thisAccount.accountId == account.accountId
case _ => false
}
// 6. Complex amount and type check
transactionOpt.exists(txn =>
txn.amount.toDouble >= 100.0 &&
txn.amount.toDouble <= 5000.0 &&
txn.transactionType == "DEBIT"
)
// 7. Check recent transaction (within 30 days)
import java.time.Instant
import java.time.temporal.ChronoUnit
transactionOpt.exists(txn => {
val now = Instant.now()
val txnInstant = txn.finishDate.toInstant
ChronoUnit.DAYS.between(txnInstant, now) <= 30
})
```
---
## 11. customerOpt (Option[Customer])
Optional customer context. Present when `customer_id` is provided in the API request.
### Available Properties (when present)
| Property | Type | Description | Example |
|----------|------|-------------|---------|
| `customerId` | `String` | Unique customer identifier (UUID) | `"cust-456-789"` |
| `bankId` | `String` | Bank identifier | `"gh.29.uk"` |
| `number` | `String` | Customer number (bank's identifier) | `"CUST123456"` |
| `legalName` | `String` | Legal name of customer | `"Alice Jane Smith"` |
| `mobileNumber` | `String` | Mobile phone number | `"+44 7700 900000"` |
| `email` | `String` | Email address | `"alice@example.com"` |
| `faceImage` | `CustomerFaceImageTrait` | Face image information | `CustomerFaceImage(...)` |
| `dateOfBirth` | `Date` | Date of birth | `Date(1990, 1, 1)` |
| `relationshipStatus` | `String` | Marital status | `"Single"`, `"Married"` |
| `dependents` | `Integer` | Number of dependents | `2` |
| `dobOfDependents` | `List[Date]` | Dates of birth of dependents | `List(Date(...))` |
| `highestEducationAttained` | `String` | Education level | `"Bachelor's Degree"` |
| `employmentStatus` | `String` | Employment status | `"Employed"`, `"Self-Employed"` |
| `creditRating` | `CreditRatingTrait` | Credit rating information | `CreditRating(...)` |
| `creditLimit` | `AmountOfMoneyTrait` | Credit limit | `AmountOfMoney(...)` |
| `kycStatus` | `Boolean` | KYC verification status | `true` or `false` |
| `lastOkDate` | `Date` | Last OK date | `Date(...)` |
| `title` | `String` | Title | `"Mr"`, `"Ms"`, `"Dr"` |
| `branchId` | `String` | Branch identifier | `"branch-123"` |
| `nameSuffix` | `String` | Name suffix | `"Jr"`, `"III"` |
### Example Rules Using customerOpt
```scala
// 1. Check KYC status
customerOpt.exists(customer => customer.kycStatus == true)
// 2. Check customer belongs to bank
customerOpt.exists(customer => customer.bankId == "gh.29.uk")
// 3. Check customer age (over 18)
import java.time.LocalDate
import java.time.Period
import java.time.ZoneId
customerOpt.exists(customer => {
val today = LocalDate.now()
val birthDate = LocalDate.ofInstant(customer.dateOfBirth.toInstant, ZoneId.systemDefault())
Period.between(birthDate, today).getYears >= 18
})
// 4. Check employment status
customerOpt.exists(customer =>
customer.employmentStatus == "Employed" ||
customer.employmentStatus == "Self-Employed"
)
// 5. Check customer email matches user
customerOpt.exists(customer => customer.email == user.emailAddress)
// 6. Check number of dependents
customerOpt.exists(customer => customer.dependents <= 3)
// 7. Check education level
customerOpt.exists(customer =>
customer.highestEducationAttained.contains("Degree")
)
// 8. Verify customer and account belong to same bank
(customerOpt, accountOpt) match {
case (Some(customer), Some(account)) =>
customer.bankId == account.bankId.value
case _ => false
}
// 9. Check mobile number is provided
customerOpt.exists(customer =>
customer.mobileNumber.nonEmpty && customer.mobileNumber != ""
)
```
---
## Complex Rule Examples
### Example 1: Multi-Object Validation
```scala
// Allow if:
// - Authenticated user is admin, OR
// - Authenticated user has finance department attribute, OR
// - User matches authenticated user AND account has sufficient balance
val isAdmin = authenticatedUser.emailAddress.endsWith("@admin.com")
val isFinance = authenticatedUserAttributes.exists(attr =>
attr.name == "department" && attr.value == "finance"
)
val isSelfAccess = user.userId == authenticatedUser.userId
val hasBalance = accountOpt.exists(_.balance.toDouble > 1000.0)
isAdmin || isFinance || (isSelfAccess && hasBalance)
```
### Example 2: Delegation Check with Attributes
```scala
// Allow if customer service is acting on behalf of someone with proper attributes
val isCustomerService = authenticatedUser.emailAddress.contains("@customerservice.com")
val hasDelegation = onBehalfOfUserOpt.isDefined
val onBehalfHasRole = onBehalfOfUserAttributes.exists(attr =>
attr.name == "role" && List("customer", "premium_customer").contains(attr.value)
)
val onBehalfHasSession = onBehalfOfUserAuthContext.exists(_.key == "session_id")
isCustomerService && hasDelegation && onBehalfHasRole && onBehalfHasSession
```
### Example 3: Transaction Approval Based on Customer
```scala
// Allow transaction if:
// - Customer is KYC verified AND
// - Transaction is under limit AND
// - Transaction currency matches account
(customerOpt, transactionOpt, accountOpt) match {
case (Some(customer), Some(txn), Some(account)) =>
val isKycVerified = customer.kycStatus == true
val underLimit = txn.amount.toDouble < 10000.0
val correctCurrency = txn.currency == account.currency
isKycVerified && underLimit && correctCurrency
case _ => false
}
```
### Example 4: Bank-Specific Rules
```scala
// Different rules for different banks
bankOpt match {
case Some(bank) if bank.bankId.value.startsWith("gh.") =>
// UK bank rules - require higher balance
accountOpt.exists(_.balance.toDouble > 5000.0)
case Some(bank) if bank.bankId.value.startsWith("us.") =>
// US bank rules - require KYC
customerOpt.exists(_.kycStatus == true)
case Some(_) =>
// Other banks - basic check
user.emailAddress.nonEmpty
case None =>
// No bank context - deny
false
}
```
---
## Working with Optional Objects
All objects except `authenticatedUser`, `authenticatedUserAttributes`, `authenticatedUserAuthContext`, `onBehalfOfUserAttributes`, `onBehalfOfUserAuthContext`, and `user` are optional. Here are patterns for working with them:
### Pattern 1: exists()
```scala
// Check if bank exists and has a property
bankOpt.exists(bank => bank.bankId.value == "gh.29.uk")
```
### Pattern 2: Pattern Matching
```scala
// Match on multiple objects simultaneously
(bankOpt, accountOpt) match {
case (Some(bank), Some(account)) =>
bank.bankId == account.bankId
case _ => false
}
```
### Pattern 3: isDefined / isEmpty
```scala
// Check if object is provided
if (bankOpt.isDefined) {
val bank = bankOpt.get
bank.bankId.value == "gh.29.uk"
} else {
false
}
```
### Pattern 4: for Comprehension
```scala
// Chain multiple optional checks
val result = for {
bank <- bankOpt
account <- accountOpt
if bank.bankId == account.bankId
if account.balance.toDouble > 1000.0
} yield true
result.getOrElse(false)
```
---
## Common Patterns and Best Practices
### 1. Type Conversions
```scala
// BigDecimal to Double
account.balance.toDouble
// Date comparisons
txn.finishDate.before(new Date())
txn.finishDate.after(new Date())
// String to numeric
account.number.toLong
```
### 2. String Operations
```scala
// Case-insensitive comparison
user.emailAddress.toLowerCase == "alice@example.com"
// Contains check
bank.fullName.contains("Bank")
// Starts with / Ends with
user.emailAddress.endsWith("@admin.com")
bank.bankId.value.startsWith("gh.")
```
### 3. List Operations
```scala
// Check if list is empty
customer.dobOfDependents.isEmpty
// Check list size
customer.dobOfDependents.length > 0
// Find in list
account.accountRoutings.exists(routing => routing.scheme == "IBAN")
```
### 4. Safe Navigation
```scala
// Use getOrElse for defaults
txn.description.getOrElse("No description")
// Chain optional operations
txn.description.getOrElse("No description").toLowerCase.contains("payment")
```
---
## Import Statements Available
These imports are automatically available in your ABAC rule code:
```scala
import com.openbankproject.commons.model._
import code.model.dataAccess.ResourceUser
import net.liftweb.common._
```
You can also use standard Scala/Java imports:
```scala
import java.time._
import java.util.Date
import scala.util._
```
---
## Summary
- **authenticatedUser**: Always available - the logged in user
- **authenticatedUserAttributes**: Always available - list of non-personal attributes for authenticated user (may be empty)
- **authenticatedUserAuthContext**: Always available - list of auth context for authenticated user (may be empty)
- **onBehalfOfUserOpt**: Optional - present when delegation is used
- **onBehalfOfUserAttributes**: Always available - list of non-personal attributes for onBehalfOf user (empty if no delegation)
- **onBehalfOfUserAuthContext**: Always available - list of auth context for onBehalfOf user (empty if no delegation)
- **user**: Always available - a user object
- **bankOpt, accountOpt, transactionOpt, customerOpt**: Optional - use `.exists()` or pattern matching
- **Type conversions**: Remember `.toDouble` for BigDecimal, `.value` for ID types
- **Safe access**: Use `getOrElse()` for Option fields
- **Build incrementally**: Break complex rules into named parts
- **READ-ONLY**: All objects are read-only - you cannot modify them in rules
---
**Last Updated:** 2024
**Related Documentation:** ABAC_SIMPLE_GUIDE.md, ABAC_REFACTORING.md, ABAC_TESTING_EXAMPLES.md

View File

@ -1,267 +0,0 @@
# ABAC Rule Parameters - Complete Reference
This document lists all 16 parameters available in ABAC (Attribute-Based Access Control) rules.
## Overview
ABAC rules receive **18 parameters** that provide complete context for access control decisions.
**All parameters are READ-ONLY** - you can only read and evaluate, never modify.
## Complete Parameter List
| # | Parameter | Type | Always Available? | Description |
|---|-----------|------|-------------------|-------------|
| 1 | `authenticatedUser` | `User` | ✅ Yes | The user who is logged in and making the API call |
| 2 | `authenticatedUserAttributes` | `List[UserAttribute]` | ✅ Yes | Non-personal attributes for the authenticated user (may be empty) |
| 3 | `authenticatedUserAuthContext` | `List[UserAuthContext]` | ✅ Yes | Auth context for the authenticated user (may be empty) |
| 4 | `onBehalfOfUserOpt` | `Option[User]` | ❌ Optional | User being represented in delegation scenarios |
| 5 | `onBehalfOfUserAttributes` | `List[UserAttribute]` | ✅ Yes | Non-personal attributes for onBehalfOf user (empty if no delegation) |
| 6 | `onBehalfOfUserAuthContext` | `List[UserAuthContext]` | ✅ Yes | Auth context for onBehalfOf user (empty if no delegation) |
| 7 | `userOpt` | `Option[User]` | ❌ Optional | A user object (when user_id is provided) |
| 8 | `userAttributes` | `List[UserAttribute]` | ✅ Yes | Non-personal attributes for user (empty if no user) |
| 9 | `bankOpt` | `Option[Bank]` | ❌ Optional | Bank object (when bank_id is provided) |
| 10 | `bankAttributes` | `List[BankAttributeTrait]` | ✅ Yes | Attributes for bank (empty if no bank) |
| 11 | `accountOpt` | `Option[BankAccount]` | ❌ Optional | Account object (when account_id is provided) |
| 12 | `accountAttributes` | `List[AccountAttribute]` | ✅ Yes | Attributes for account (empty if no account) |
| 13 | `transactionOpt` | `Option[Transaction]` | ❌ Optional | Transaction object (when transaction_id is provided) |
| 14 | `transactionAttributes` | `List[TransactionAttribute]` | ✅ Yes | Attributes for transaction (empty if no transaction) |
| 15 | `transactionRequestOpt` | `Option[TransactionRequest]` | ❌ Optional | Transaction request object (when transaction_request_id is provided) |
| 16 | `transactionRequestAttributes` | `List[TransactionRequestAttributeTrait]` | ✅ Yes | Attributes for transaction request (empty if no transaction request) |
| 17 | `customerOpt` | `Option[Customer]` | ❌ Optional | Customer object (when customer_id is provided) |
| 18 | `customerAttributes` | `List[CustomerAttribute]` | ✅ Yes | Attributes for customer (empty if no customer) |
## Function Signature
```scala
type AbacRuleFunction = (
User, // 1. authenticatedUser
List[UserAttribute], // 2. authenticatedUserAttributes
List[UserAuthContext], // 3. authenticatedUserAuthContext
Option[User], // 4. onBehalfOfUserOpt
List[UserAttribute], // 5. onBehalfOfUserAttributes
List[UserAuthContext], // 6. onBehalfOfUserAuthContext
Option[User], // 7. userOpt
List[UserAttribute], // 8. userAttributes
Option[Bank], // 9. bankOpt
List[BankAttributeTrait], // 10. bankAttributes
Option[BankAccount], // 11. accountOpt
List[AccountAttribute], // 12. accountAttributes
Option[Transaction], // 13. transactionOpt
List[TransactionAttribute], // 14. transactionAttributes
Option[TransactionRequest], // 15. transactionRequestOpt
List[TransactionRequestAttributeTrait], // 16. transactionRequestAttributes
Option[Customer], // 17. customerOpt
List[CustomerAttribute] // 18. customerAttributes
) => Boolean
```
## Parameter Groups
### Group 1: Authenticated User (Always Available)
- `authenticatedUser` - The logged in user
- `authenticatedUserAttributes` - Their non-personal attributes
- `authenticatedUserAuthContext` - Their auth context (session, IP, etc.)
### Group 2: OnBehalfOf User (Delegation)
- `onBehalfOfUserOpt` - Optional delegated user
- `onBehalfOfUserAttributes` - Their non-personal attributes (empty if no delegation)
- `onBehalfOfUserAuthContext` - Their auth context (empty if no delegation)
### Group 3: Target User (Optional)
- `userOpt` - Optional user object
- `userAttributes` - Their non-personal attributes (empty if no user)
### Group 4: Bank (Optional)
- `bankOpt` - Optional bank object
- `bankAttributes` - Bank attributes (empty if no bank)
### Group 5: Account (Optional)
- `accountOpt` - Optional account object
- `accountAttributes` - Account attributes (empty if no account)
### Group 6: Transaction (Optional)
- `transactionOpt` - Optional transaction object
- `transactionAttributes` - Transaction attributes (empty if no transaction)
### Group 7: Transaction Request (Optional)
- `transactionRequestOpt` - Optional transaction request object
- `transactionRequestAttributes` - Transaction request attributes (empty if no transaction request)
### Group 8: Customer (Optional)
- `customerOpt` - Optional customer object
- `customerAttributes` - Customer attributes (empty if no customer)
## Example Rules
### Example 1: Check Authenticated User Attribute
```scala
authenticatedUserAttributes.exists(attr =>
attr.name == "department" && attr.value == "finance"
)
```
### Example 2: Check Bank Attribute
```scala
bankAttributes.exists(attr =>
attr.name == "country" && attr.value == "UK"
)
```
### Example 3: Check Account Attribute
```scala
accountAttributes.exists(attr =>
attr.name == "account_type" && attr.value == "premium"
)
```
### Example 4: Check Transaction Attribute
```scala
transactionAttributes.exists(attr =>
attr.name == "risk_score" &&
attr.value.toIntOption.exists(_ < 5)
)
```
### Example 5: Check Transaction Request Attribute
```scala
transactionRequestAttributes.exists(attr =>
attr.name == "approval_status" && attr.value == "pending"
)
```
### Example 6: Check Customer Attribute
```scala
customerAttributes.exists(attr =>
attr.name == "kyc_status" && attr.value == "verified"
)
```
### Example 7: Complex Multi-Attribute Rule
```scala
// Allow if:
// - Authenticated user is in finance department
// - Bank is in allowed countries
// - Account is premium
// - Transaction risk is low
val authIsFinance = authenticatedUserAttributes.exists(attr =>
attr.name == "department" && attr.value == "finance"
)
val bankAllowed = bankAttributes.exists(attr =>
attr.name == "country" && List("UK", "US", "DE").contains(attr.value)
)
val accountPremium = accountAttributes.exists(attr =>
attr.name == "account_type" && attr.value == "premium"
)
val lowRisk = transactionAttributes.exists(attr =>
attr.name == "risk_score" && attr.value.toIntOption.exists(_ < 3)
)
authIsFinance && bankAllowed && accountPremium && lowRisk
```
### Example 8: Delegation with Attributes
```scala
// Allow customer service to help premium customers
val isCustomerService = authenticatedUserAttributes.exists(attr =>
attr.name == "role" && attr.value == "customer_service"
)
val hasDelegation = onBehalfOfUserOpt.isDefined
val customerIsPremium = onBehalfOfUserAttributes.exists(attr =>
attr.name == "customer_tier" && attr.value == "premium"
)
isCustomerService && hasDelegation && customerIsPremium
```
## API Request Mapping
When you make an API request:
```json
{
"authenticated_user_id": "alice@example.com",
"on_behalf_of_user_id": "bob@example.com",
"user_id": "charlie@example.com",
"bank_id": "gh.29.uk",
"account_id": "acc-123",
"transaction_id": "txn-456",
"transaction_request_id": "tr-123",
"customer_id": "cust-789"
}
```
The engine automatically:
1. Fetches `authenticatedUser` using `authenticated_user_id` (or from auth token if not provided)
2. Fetches `authenticatedUserAttributes` and `authenticatedUserAuthContext` for authenticated user
3. Fetches `onBehalfOfUserOpt`, `onBehalfOfUserAttributes`, `onBehalfOfUserAuthContext` if `on_behalf_of_user_id` provided
4. Fetches `userOpt` and `userAttributes` if `user_id` provided
5. Fetches `bankOpt` and `bankAttributes` if `bank_id` provided
6. Fetches `accountOpt` and `accountAttributes` if `account_id` provided
7. Fetches `transactionOpt` and `transactionAttributes` if `transaction_id` provided
8. Fetches `transactionRequestOpt` and `transactionRequestAttributes` if `transaction_request_id` provided
9. Fetches `customerOpt` and `customerAttributes` if `customer_id` provided
## Working with Attributes
All attribute lists follow the same pattern:
```scala
// Check if attribute exists with specific value
attributeList.exists(attr => attr.name == "key" && attr.value == "value")
// Check if list is empty
attributeList.isEmpty
// Check if list has any attributes
attributeList.nonEmpty
// Find specific attribute
attributeList.find(_.name == "key").map(_.value)
// Multiple attributes (AND)
val hasAttr1 = attributeList.exists(_.name == "key1")
val hasAttr2 = attributeList.exists(_.name == "key2")
hasAttr1 && hasAttr2
// Multiple attributes (OR)
attributeList.exists(attr =>
List("key1", "key2", "key3").contains(attr.name)
)
```
## Key Points
**18 parameters total** - comprehensive context for access decisions
**3 always available objects** - authenticatedUser, authenticatedUserAttributes, authenticatedUserAuthContext
**15 contextual parameters** - available based on what IDs are provided in the request
**All READ-ONLY** - cannot modify any parameter values
**Automatic fetching** - engine fetches all data based on provided IDs
**Type safety** - optional objects use `Option[T]`, lists are `List[T]`
**Empty lists not None** - attribute lists are always available, just empty when no data
## Summary
ABAC rules have access to:
- **3 user contexts**: authenticated, onBehalfOf, and target user
- **5 resource contexts**: bank, account, transaction, transaction request, customer
- **Complete attribute data**: for all users and resources
- **Auth context**: session, IP, device info, etc.
- **Full type safety**: optional objects and guaranteed lists
This provides everything needed to make sophisticated access control decisions!
---
**Related Documentation:**
- `ABAC_OBJECT_PROPERTIES_REFERENCE.md` - Detailed property reference for each object
- `ABAC_SIMPLE_GUIDE.md` - Getting started guide
- `ABAC_REFACTORING.md` - Technical implementation details
**Last Updated:** 2024

View File

@ -1,354 +0,0 @@
# ABAC Rules Engine - Simple Guide
## Overview
The ABAC (Attribute-Based Access Control) Rules Engine allows you to create dynamic access control rules in Scala that evaluate whether a user should have access to a resource.
## Core Concept
**One Rule + One Execution Method = Simple Access Control**
```scala
def executeRule(
ruleId: String,
authenticatedUserId: String,
onBehalfOfUserId: Option[String] = None,
userId: Option[String] = None,
callContext: Option[CallContext] = None,
bankId: Option[String] = None,
accountId: Option[String] = None,
viewId: Option[String] = None,
transactionId: Option[String] = None,
customerId: Option[String] = None
): Box[Boolean]
```
---
## Understanding the Three User Parameters
### 1. `authenticatedUserId` (Required)
**The person actually logged in and making the API call**
- This is ALWAYS the real user who authenticated
- Retrieved from the authentication token
- Cannot be faked or changed
**Example:** Alice logs into the banking app
- `authenticatedUserId = "alice@example.com"`
---
### 2. `onBehalfOfUserId` (Optional)
**When someone acts on behalf of another user (delegation)**
- Used for delegation scenarios
- The authenticated user is acting for someone else
- Common in customer service, admin tools, power of attorney
**Example:** Customer service rep Bob helps Alice with her account
- `authenticatedUserId = "bob@customerservice.com"` (the rep logged in)
- `onBehalfOfUserId = "alice@example.com"` (helping Alice)
- `userId = "alice@example.com"` (checking Alice's permissions)
---
### 3. `userId` (Optional)
**The target user being evaluated by the rule**
- Defaults to `authenticatedUserId` if not provided
- The user whose permissions/attributes are being checked
- Useful for testing rules for different users
**Example:** Admin checking if Alice can access an account
- `authenticatedUserId = "admin@example.com"` (admin is logged in)
- `userId = "alice@example.com"` (checking Alice's access)
---
## Common Scenarios
### Scenario 1: Normal User Access
**Alice wants to view her own account**
```json
{
"bank_id": "gh.29.uk",
"account_id": "alice-account-123"
}
```
Behind the scenes:
- `authenticatedUserId = "alice@example.com"` (from auth token)
- `onBehalfOfUserId = None`
- `userId = None` → defaults to Alice
**Rule example:**
```scala
// Check if user owns the account
accountOpt.exists(account =>
account.owners.exists(owner => owner.userId == user.userId)
)
```
---
### Scenario 2: Customer Service Delegation
**Bob (customer service) helps Alice view her account**
```json
{
"on_behalf_of_user_id": "alice@example.com",
"bank_id": "gh.29.uk",
"account_id": "alice-account-123"
}
```
Behind the scenes:
- `authenticatedUserId = "bob@customerservice.com"` (from auth token)
- `onBehalfOfUserId = "alice@example.com"`
- `userId = None` → defaults to Bob, but rule can check both
**Rule example:**
```scala
// Allow if authenticated user is customer service AND acting on behalf of an account owner
val isCustomerService = authenticatedUser.emailAddress.contains("@customerservice.com")
val hasValidDelegation = onBehalfOfUserOpt.isDefined
val targetOwnsAccount = accountOpt.exists(account =>
account.owners.exists(owner => owner.userId == user.userId)
)
isCustomerService && hasValidDelegation && targetOwnsAccount
```
---
### Scenario 3: Admin Testing
**Admin wants to test if Alice can access an account (without logging in as Alice)**
```json
{
"user_id": "alice@example.com",
"bank_id": "gh.29.uk",
"account_id": "alice-account-123"
}
```
Behind the scenes:
- `authenticatedUserId = "admin@example.com"` (from auth token)
- `onBehalfOfUserId = None`
- `userId = "alice@example.com"` (evaluating for Alice)
**Rule example:**
```scala
// Allow admins to test access, or allow if user owns account
val isAdmin = authenticatedUser.emailAddress.endsWith("@admin.com")
val userOwnsAccount = accountOpt.exists(account =>
account.owners.exists(owner => owner.userId == user.userId)
)
isAdmin || userOwnsAccount
```
---
## API Usage
### Endpoint
```
POST /obp/v6.0.0/management/abac-rules/{RULE_ID}/execute
```
### Request Examples
#### Example 1: Basic Access Check
```json
{
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"
}
```
- Checks if authenticated user can access the account
#### Example 2: Delegation
```json
{
"on_behalf_of_user_id": "alice@example.com",
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"
}
```
- Authenticated user acting on behalf of Alice
#### Example 3: Testing for Different User
```json
{
"user_id": "bob@example.com",
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"
}
```
- Check if Bob can access the account (useful for admins testing)
#### Example 4: Complex Scenario
```json
{
"on_behalf_of_user_id": "alice@example.com",
"user_id": "charlie@example.com",
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0",
"transaction_id": "trans-123"
}
```
- Authenticated user acting on behalf of Alice
- Checking if Charlie can access account and transaction
---
## Writing ABAC Rules
### Available Objects in Rules
```scala
// These are available in your rule code:
authenticatedUser: User // Always present - the logged in user
onBehalfOfUserOpt: Option[User] // Present if delegation
user: User // Always present - the target user being evaluated
bankOpt: Option[Bank] // Present if bank_id provided
accountOpt: Option[BankAccount] // Present if account_id provided
transactionOpt: Option[Transaction] // Present if transaction_id provided
customerOpt: Option[Customer] // Present if customer_id provided
```
### Simple Rule Examples
#### Rule 1: User Must Own Account
```scala
accountOpt.exists(account =>
account.owners.exists(owner => owner.userId == user.userId)
)
```
#### Rule 2: Admin or Owner
```scala
val isAdmin = authenticatedUser.emailAddress.endsWith("@admin.com")
val isOwner = accountOpt.exists(account =>
account.owners.exists(owner => owner.userId == user.userId)
)
isAdmin || isOwner
```
#### Rule 3: Customer Service Delegation
```scala
val isCustomerService = authenticatedUser.emailAddress.contains("@customerservice.com")
val actingOnBehalf = onBehalfOfUserOpt.isDefined
val userIsOwner = accountOpt.exists(account =>
account.owners.exists(owner => owner.userId == user.userId)
)
// Allow if customer service is helping an account owner
isCustomerService && actingOnBehalf && userIsOwner
```
#### Rule 4: Self-Service Only (No Delegation)
```scala
// User must be checking their own access (no delegation allowed)
val isSelfService = authenticatedUser.userId == user.userId
val noDelegation = onBehalfOfUserOpt.isEmpty
isSelfService && noDelegation
```
#### Rule 5: Account Balance Check
```scala
accountOpt.exists(account => account.balance.toDouble >= 1000.0)
```
---
## Quick Reference Table
| Parameter | Required? | Purpose | Example Value |
|-----------|-----------|---------|---------------|
| `authenticatedUserId` | ✅ Yes | Who is logged in | `"alice@example.com"` |
| `onBehalfOfUserId` | ❌ Optional | Delegation | `"bob@example.com"` |
| `userId` | ❌ Optional | Target user to evaluate | `"charlie@example.com"` |
| `bankId` | ❌ Optional | Bank context | `"gh.29.uk"` |
| `accountId` | ❌ Optional | Account context | `"acc-123"` |
| `viewId` | ❌ Optional | View context | `"owner"` |
| `transactionId` | ❌ Optional | Transaction context | `"trans-456"` |
| `customerId` | ❌ Optional | Customer context | `"cust-789"` |
---
## Real-World Use Cases
### Use Case 1: Personal Banking
- User logs in → `authenticatedUserId`
- Views their own account → `userId` defaults to authenticated user
- Rule checks ownership
### Use Case 2: Business Banking with Delegates
- CFO logs in → `authenticatedUserId = "cfo@company.com"`
- Checks on behalf of CEO → `onBehalfOfUserId = "ceo@company.com"`
- System evaluates if CEO has access → `userId = "ceo@company.com"`
### Use Case 3: Customer Support
- Support agent logs in → `authenticatedUserId = "agent@bank.com"`
- Helps customer → `onBehalfOfUserId = "customer@example.com"`
- Rule verifies: agent has support role AND customer owns account
### Use Case 4: Admin Panel
- Admin logs in → `authenticatedUserId = "admin@bank.com"`
- Tests rule for any user → `userId = "testuser@example.com"`
- Rule evaluates for test user, but admin must be authenticated
---
## Testing Tips
### Test Different Users
```bash
# Test as yourself
curl -X POST .../execute -d '{"bank_id": "gh.29.uk"}'
# Test for another user (if you have permission)
curl -X POST .../execute -d '{"user_id": "other@example.com", "bank_id": "gh.29.uk"}'
```
### Test Delegation
```bash
# Act on behalf of someone
curl -X POST .../execute -d '{
"on_behalf_of_user_id": "alice@example.com",
"bank_id": "gh.29.uk"
}'
```
### Debug Your Rules
```scala
// Add simple checks to understand what's happening
val result = (authenticatedUser.userId == user.userId)
println(s"Auth user: ${authenticatedUser.userId}, Target user: ${user.userId}, Match: $result")
result
```
---
## Summary
**Keep it simple**: One execution method, clear parameters
**Three user IDs**: authenticated (who), on-behalf-of (delegation), user (target)
**Write rules in Scala**: Full power of the language
**Test via API**: Just pass IDs, objects fetched automatically
**Flexible**: Supports normal access, delegation, and admin testing
---
**Related Documentation:**
- `ABAC_OBJECT_PROPERTIES_REFERENCE.md` - Full list of available properties
- `ABAC_TESTING_EXAMPLES.md` - More testing examples
- `ABAC_REFACTORING.md` - Technical implementation details
**Last Updated:** 2024

View File

@ -1,622 +0,0 @@
# ABAC Rule Testing Examples
This document provides practical examples for testing ABAC (Attribute-Based Access Control) rules using the refactored ID-based API.
## Prerequisites
1. You need a valid DirectLogin token or other authentication method
2. You must have the `canExecuteAbacRule` entitlement
3. You need to know the IDs of:
- ABAC rules you want to test
- Users, banks, accounts, transactions, customers (as needed by your rules)
## API Endpoint
```
POST /obp/v6.0.0/management/abac-rules/{RULE_ID}/execute
```
## Basic Examples
### Example 1: Simple User-Only Rule
Test a rule that only checks user attributes (no bank/account context needed).
**Rule Code:**
```scala
// Rule: Only allow admin users
user.userId.endsWith("@admin.com")
```
**Test Request:**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/admin-only-rule/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{}'
```
**Response:**
```json
{
"rule_id": "admin-only-rule",
"rule_name": "Admin Only Access",
"result": true,
"message": "Access granted"
}
```
### Example 2: Test Rule for Different User
Test how the rule behaves for a different user (without re-authenticating).
**Test Request:**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/admin-only-rule/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{
"user_id": "alice@example.com"
}'
```
**Response:**
```json
{
"rule_id": "admin-only-rule",
"rule_name": "Admin Only Access",
"result": false,
"message": "Access denied"
}
```
### Example 3: Bank-Specific Rule
Test a rule that checks bank context.
**Rule Code:**
```scala
// Rule: Only allow access to UK banks
bankOpt.exists(bank =>
bank.bankId.value.startsWith("gh.") ||
bank.bankId.value.startsWith("uk.")
)
```
**Test Request:**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/uk-banks-only/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{
"bank_id": "gh.29.uk"
}'
```
**Response:**
```json
{
"rule_id": "uk-banks-only",
"rule_name": "UK Banks Only",
"result": true,
"message": "Access granted"
}
```
### Example 4: Account Balance Rule
Test a rule that checks account balance.
**Rule Code:**
```scala
// Rule: Only allow if account balance > 1000
accountOpt.exists(account =>
account.balance.toDouble > 1000.0
)
```
**Test Request:**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/high-balance-only/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"
}'
```
**Response:**
```json
{
"rule_id": "high-balance-only",
"rule_name": "High Balance Only",
"result": true,
"message": "Access granted"
}
```
### Example 5: Account Ownership Rule
Test a rule that checks if user owns the account.
**Rule Code:**
```scala
// Rule: User must own the account
accountOpt.exists(account =>
account.owners.exists(owner => owner.userId == user.userId)
)
```
**Test Request:**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/account-owner-only/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{
"user_id": "alice@example.com",
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"
}'
```
### Example 6: Transaction Amount Rule
Test a rule that checks transaction amount.
**Rule Code:**
```scala
// Rule: Only allow transactions under 10000
transactionOpt.exists(txn =>
txn.amount.toDouble < 10000.0
)
```
**Test Request:**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/small-transactions/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0",
"transaction_id": "trans-123"
}'
```
### Example 7: Customer Credit Rating Rule
Test a rule that checks customer credit rating.
**Rule Code:**
```scala
// Rule: Only allow customers with excellent credit
customerOpt.exists(customer =>
customer.creditRating.getOrElse("") == "EXCELLENT"
)
```
**Test Request:**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/excellent-credit-only/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{
"bank_id": "gh.29.uk",
"customer_id": "cust-456"
}'
```
## Complex Examples
### Example 8: Multi-Condition Rule
Test a complex rule with multiple conditions.
**Rule Code:**
```scala
// Rule: Allow if:
// - User is admin, OR
// - User owns account AND balance > 100 AND account is at UK bank
val isAdmin = user.userId.endsWith("@admin.com")
val ownsAccount = accountOpt.exists(_.owners.exists(_.userId == user.userId))
val hasBalance = accountOpt.exists(_.balance.toDouble > 100.0)
val isUKBank = bankOpt.exists(b =>
b.bankId.value.startsWith("gh.") || b.bankId.value.startsWith("uk.")
)
isAdmin || (ownsAccount && hasBalance && isUKBank)
```
**Test Request (Admin User):**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/complex-access/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{
"user_id": "admin@admin.com",
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"
}'
```
**Test Request (Regular User):**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/complex-access/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{
"user_id": "alice@example.com",
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"
}'
```
### Example 9: Time-Based Rule
Test a rule that includes time-based logic.
**Rule Code:**
```scala
// Rule: Only allow during business hours (9 AM - 5 PM) unless user is admin
import java.time.LocalTime
import java.time.ZoneId
val now = LocalTime.now(ZoneId.of("Europe/London"))
val isBusinessHours = now.isAfter(LocalTime.of(9, 0)) && now.isBefore(LocalTime.of(17, 0))
val isAdmin = user.userId.endsWith("@admin.com")
isAdmin || isBusinessHours
```
**Test Request:**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/business-hours-only/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{
"user_id": "alice@example.com"
}'
```
### Example 10: Cross-Entity Validation
Test a rule that validates relationships between entities.
**Rule Code:**
```scala
// Rule: Customer must be associated with the same bank as the account
(customerOpt, accountOpt, bankOpt) match {
case (Some(customer), Some(account), Some(bank)) =>
customer.bankId == bank.bankId &&
account.bankId == bank.bankId
case _ => false
}
```
**Test Request:**
```bash
curl -X POST \
'https://api.openbankproject.com/obp/v6.0.0/management/abac-rules/cross-entity-validation/execute' \
-H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \
-H 'Content-Type: application/json' \
-d '{
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0",
"customer_id": "cust-456"
}'
```
## Testing Patterns
### Pattern 1: Test Multiple Users
Test the same rule for different users to verify behavior:
```bash
# Test for admin
curl -X POST 'https://.../execute' -d '{"user_id": "admin@admin.com", "bank_id": "gh.29.uk"}'
# Test for regular user
curl -X POST 'https://.../execute' -d '{"user_id": "alice@example.com", "bank_id": "gh.29.uk"}'
# Test for another user
curl -X POST 'https://.../execute' -d '{"user_id": "bob@example.com", "bank_id": "gh.29.uk"}'
```
### Pattern 2: Test Different Banks
Test how the rule behaves across different banks:
```bash
# UK Bank
curl -X POST 'https://.../execute' -d '{"bank_id": "gh.29.uk", "account_id": "acc1"}'
# US Bank
curl -X POST 'https://.../execute' -d '{"bank_id": "us.bank.01", "account_id": "acc2"}'
# German Bank
curl -X POST 'https://.../execute' -d '{"bank_id": "de.bank.01", "account_id": "acc3"}'
```
### Pattern 3: Test Edge Cases
Test boundary conditions:
```bash
# No context (minimal)
curl -X POST 'https://.../execute' -d '{}'
# Partial context
curl -X POST 'https://.../execute' -d '{"bank_id": "gh.29.uk"}'
# Full context
curl -X POST 'https://.../execute' -d '{
"user_id": "alice@example.com",
"bank_id": "gh.29.uk",
"account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0",
"transaction_id": "trans-123",
"customer_id": "cust-456"
}'
# Invalid IDs (should handle gracefully)
curl -X POST 'https://.../execute' -d '{"bank_id": "invalid-bank-id"}'
```
### Pattern 4: Automated Testing Script
Create a bash script to test multiple scenarios:
```bash
#!/bin/bash
API_BASE="https://api.openbankproject.com/obp/v6.0.0"
TOKEN="eyJhbGciOiJIUzI1..."
RULE_ID="my-test-rule"
test_rule() {
local description=$1
local payload=$2
echo "Testing: $description"
curl -s -X POST \
"$API_BASE/management/abac-rules/$RULE_ID/execute" \
-H "Authorization: DirectLogin token=$TOKEN" \
-H "Content-Type: application/json" \
-d "$payload" | jq '.result, .message'
echo "---"
}
# Run tests
test_rule "Admin user" '{"user_id": "admin@admin.com"}'
test_rule "Regular user" '{"user_id": "alice@example.com"}'
test_rule "With bank context" '{"user_id": "alice@example.com", "bank_id": "gh.29.uk"}'
test_rule "With account context" '{"user_id": "alice@example.com", "bank_id": "gh.29.uk", "account_id": "acc1"}'
```
## Error Scenarios
### Error 1: Rule Not Found
```bash
curl -X POST 'https://.../management/abac-rules/nonexistent-rule/execute' \
-H 'Authorization: DirectLogin token=...' \
-d '{}'
```
**Response:**
```json
{
"code": 404,
"message": "ABAC Rule not found with ID: nonexistent-rule"
}
```
### Error 2: Inactive Rule
If the rule exists but is not active:
**Response:**
```json
{
"rule_id": "inactive-rule",
"rule_name": "Inactive Rule",
"result": false,
"message": "Execution error: ABAC Rule Inactive Rule is not active"
}
```
### Error 3: Invalid User ID
```bash
curl -X POST 'https://.../execute' \
-H 'Authorization: DirectLogin token=...' \
-d '{"user_id": "nonexistent-user"}'
```
**Response:**
```json
{
"rule_id": "test-rule",
"rule_name": "Test Rule",
"result": false,
"message": "Execution error: User not found"
}
```
### Error 4: Compilation Error
If the rule has invalid Scala code:
**Response:**
```json
{
"rule_id": "broken-rule",
"rule_name": "Broken Rule",
"result": false,
"message": "Execution error: Failed to compile ABAC rule: ..."
}
```
## Python Testing Example
```python
import requests
import json
class AbacRuleTester:
def __init__(self, base_url, token):
self.base_url = base_url
self.headers = {
'Authorization': f'DirectLogin token={token}',
'Content-Type': 'application/json'
}
def test_rule(self, rule_id, **context):
"""Test an ABAC rule with given context"""
url = f"{self.base_url}/management/abac-rules/{rule_id}/execute"
# Filter out None values
payload = {k: v for k, v in context.items() if v is not None}
response = requests.post(url, headers=self.headers, json=payload)
return response.json()
def test_users(self, rule_id, user_ids, **context):
"""Test rule for multiple users"""
results = {}
for user_id in user_ids:
result = self.test_rule(rule_id, user_id=user_id, **context)
results[user_id] = result['result']
return results
# Usage
tester = AbacRuleTester(
base_url='https://api.openbankproject.com/obp/v6.0.0',
token='your-token-here'
)
# Test single rule
result = tester.test_rule(
'admin-only-rule',
user_id='alice@example.com',
bank_id='gh.29.uk'
)
print(f"Result: {result['result']}, Message: {result['message']}")
# Test multiple users
users = ['admin@admin.com', 'alice@example.com', 'bob@example.com']
results = tester.test_users('account-owner-rule', users,
bank_id='gh.29.uk',
account_id='acc123')
print(results)
# Output: {'admin@admin.com': True, 'alice@example.com': False, ...}
```
## JavaScript Testing Example
```javascript
class AbacRuleTester {
constructor(baseUrl, token) {
this.baseUrl = baseUrl;
this.headers = {
'Authorization': `DirectLogin token=${token}`,
'Content-Type': 'application/json'
};
}
async testRule(ruleId, context = {}) {
const url = `${this.baseUrl}/management/abac-rules/${ruleId}/execute`;
// Remove undefined values
const payload = Object.fromEntries(
Object.entries(context).filter(([_, v]) => v !== undefined)
);
const response = await fetch(url, {
method: 'POST',
headers: this.headers,
body: JSON.stringify(payload)
});
return await response.json();
}
async testUsers(ruleId, userIds, context = {}) {
const results = {};
for (const userId of userIds) {
const result = await this.testRule(ruleId, { ...context, user_id: userId });
results[userId] = result.result;
}
return results;
}
}
// Usage
const tester = new AbacRuleTester(
'https://api.openbankproject.com/obp/v6.0.0',
'your-token-here'
);
// Test single rule
const result = await tester.testRule('admin-only-rule', {
user_id: 'alice@example.com',
bank_id: 'gh.29.uk'
});
console.log(`Result: ${result.result}, Message: ${result.message}`);
// Test multiple users
const users = ['admin@admin.com', 'alice@example.com', 'bob@example.com'];
const results = await tester.testUsers('account-owner-rule', users, {
bank_id: 'gh.29.uk',
account_id: 'acc123'
});
console.log(results);
```
## Best Practices
1. **Start Simple**: Begin with rules that only check user attributes, then add complexity
2. **Test Edge Cases**: Always test with missing IDs, invalid IDs, and partial context
3. **Test Multiple Users**: Verify rule behavior for different user types (admin, owner, guest)
4. **Use Automation**: Create scripts to test multiple scenarios quickly
5. **Document Expected Behavior**: Keep track of what each test should return
6. **Test Both Paths**: Test cases that should allow access AND cases that should deny
7. **Performance Testing**: Test with realistic data volumes to ensure rules perform well
## Troubleshooting
### Rule Always Returns False
- Check if the rule is active (`is_active: true`)
- Verify the rule code compiles successfully
- Ensure all required context IDs are provided
- Check if objects are being fetched successfully
### Rule Times Out
- Rule execution has a 5-second timeout for object fetching
- Simplify rule logic or optimize database queries
- Consider caching frequently accessed objects
### Unexpected Results
- Test with `executeRuleWithObjects` to verify rule logic
- Check object availability (might be `None` if fetch fails)
- Add logging to rule code to debug decision logic
- Verify IDs are correct and objects exist in database
---
**Last Updated:** 2024
**Related Documentation:** ABAC_REFACTORING.md

View File

@ -480,6 +480,19 @@ object ApiRole extends MdcLoggable{
case class CanDeleteNonPersonalUserAttribute (requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteNonPersonalUserAttribute = CanDeleteNonPersonalUserAttribute()
// v6.0.0 User Attribute roles (consistent naming - "user attributes" means non-personal)
case class CanCreateUserAttribute (requiresBankId: Boolean = false) extends ApiRole
lazy val canCreateUserAttribute = CanCreateUserAttribute()
case class CanGetUserAttributes (requiresBankId: Boolean = false) extends ApiRole
lazy val canGetUserAttributes = CanGetUserAttributes()
case class CanUpdateUserAttribute (requiresBankId: Boolean = false) extends ApiRole
lazy val canUpdateUserAttribute = CanUpdateUserAttribute()
case class CanDeleteUserAttribute (requiresBankId: Boolean = false) extends ApiRole
lazy val canDeleteUserAttribute = CanDeleteUserAttribute()
case class CanReadUserLockedStatus(requiresBankId: Boolean = false) extends ApiRole
lazy val canReadUserLockedStatus = CanReadUserLockedStatus()

View File

@ -15,10 +15,13 @@ object ApiTag {
// When using these tags in resource docs, as we now have many APIs, it's best not to have too use too many tags per endpoint.
val apiTagOldStyle = ResourceDocTag("Old-Style")
val apiTagTransactionRequest = ResourceDocTag("Transaction-Request")
val apiTagTransactionRequestAttribute = ResourceDocTag("Transaction-Request-Attribute")
val apiTagVrp = ResourceDocTag("VRP")
val apiTagApi = ResourceDocTag("API")
val apiTagBank = ResourceDocTag("Bank")
val apiTagBankAttribute = ResourceDocTag("Bank-Attribute")
val apiTagAccount = ResourceDocTag("Account")
val apiTagAccountAttribute = ResourceDocTag("Account-Attribute")
val apiTagAccountAccess = ResourceDocTag("Account-Access")
val apiTagDirectDebit = ResourceDocTag("Direct-Debit")
val apiTagStandingOrder = ResourceDocTag("Standing-Order")
@ -30,6 +33,7 @@ object ApiTag {
val apiTagPublicData = ResourceDocTag("PublicData")
val apiTagPrivateData = ResourceDocTag("PrivateData")
val apiTagTransaction = ResourceDocTag("Transaction")
val apiTagTransactionAttribute = ResourceDocTag("Transaction-Attribute")
val apiTagTransactionFirehose = ResourceDocTag("Transaction-Firehose")
val apiTagCounterpartyMetaData = ResourceDocTag("Counterparty-Metadata")
val apiTagTransactionMetaData = ResourceDocTag("Transaction-Metadata")
@ -43,17 +47,23 @@ object ApiTag {
val apiTagCounterparty = ResourceDocTag("Counterparty")
val apiTagKyc = ResourceDocTag("KYC")
val apiTagCustomer = ResourceDocTag("Customer")
val apiTagCustomerAttribute = ResourceDocTag("Customer-Attribute")
val apiTagOnboarding = ResourceDocTag("Onboarding")
val apiTagUser = ResourceDocTag("User") // Use for User Management / Info APIs
val apiTagUserInvitation = ResourceDocTag("User-Invitation")
val apiTagUserInvitation = ResourceDocTag("User-Invitation")
val apiTagAttribute = ResourceDocTag("Attribute")
val apiTagUserAttribute = ResourceDocTag("User-Attribute")
val apiTagMeeting = ResourceDocTag("Customer-Meeting")
val apiTagExperimental = ResourceDocTag("Experimental")
val apiTagPerson = ResourceDocTag("Person")
val apiTagCard = ResourceDocTag("Card")
val apiTagCardAttribute = ResourceDocTag("Card-Attribute")
val apiTagSandbox = ResourceDocTag("Sandbox")
val apiTagBranch = ResourceDocTag("Branch")
val apiTagATM = ResourceDocTag("ATM")
val apiTagAtmAttribute = ResourceDocTag("ATM-Attribute")
val apiTagProduct = ResourceDocTag("Product")
val apiTagProductAttribute = ResourceDocTag("Product-Attribute")
val apiTagProductCollection = ResourceDocTag("Product-Collection")
val apiTagOpenData = ResourceDocTag("Open-Data")
val apiTagConsumer = ResourceDocTag("Consumer")

View File

@ -3903,6 +3903,473 @@ object Glossary extends MdcLoggable {
|""".stripMargin)
glossaryItems += GlossaryItem(
title = "ABAC_Simple_Guide",
description =
s"""
|# ABAC Rules Engine - Simple Guide
|
|## Overview
|
|The ABAC (Attribute-Based Access Control) Rules Engine allows you to create dynamic access control rules in Scala that evaluate whether a user should have access to a resource.
|
|## API Usage
|
|### Endpoint
|```
|POST $getObpApiRoot/v6.0.0/management/abac-rules/{RULE_ID}/execute
|```
|
|### Request Example
|```bash
|curl -X POST \\
| '$getObpApiRoot/v6.0.0/management/abac-rules/admin-only-rule/execute' \\
| -H 'Authorization: DirectLogin token=eyJhbGciOiJIUzI1...' \\
| -H 'Content-Type: application/json' \\
| -d '{
| "bank_id": "gh.29.uk",
| "account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"
| }'
|```
|
|## Understanding the Three User Parameters
|
|### 1. `authenticatedUserId` (Required)
|**The person actually logged in and making the API call**
|
|- The real user who authenticated
|- Retrieved from the authentication token
|
|### 2. `onBehalfOfUserId` (Optional)
|**When someone acts on behalf of another user (delegation)**
|
|- Used for delegation scenarios
|- The authenticated user is acting for someone else
|- Common in customer service, admin tools, power of attorney
|
|### 3. `userId` (Optional)
|**The target user being evaluated by the rule**
|
|- Defaults to `authenticatedUserId` if not provided
|- The user whose permissions/attributes are being checked
|- Useful for testing rules for different users
|
|## Writing ABAC Rules
|
|### Simple Rule Examples
|
|**Rule 1: User Must Own Account**
|```scala
|accountOpt.exists(account =>
| account.owners.exists(owner => owner.userId == user.userId)
|)
|```
|
|**Rule 2: Admin or Owner**
|```scala
|val isAdmin = authenticatedUser.emailAddress.endsWith("@admin.com")
|val isOwner = accountOpt.exists(account =>
| account.owners.exists(owner => owner.userId == user.userId)
|)
|
|isAdmin || isOwner
|```
|
|**Rule 3: Account Balance Check**
|```scala
|accountOpt.exists(account => account.balance.toDouble >= 1000.0)
|```
|
|## Available Objects in Rules
|
|```scala
|authenticatedUser: User // The logged in user
|onBehalfOfUserOpt: Option[User] // User being acted on behalf of (if provided)
|user: User // The target user being evaluated
|bankOpt: Option[Bank] // Bank context (if bank_id provided)
|accountOpt: Option[BankAccount] // Account context (if account_id provided)
|transactionOpt: Option[Transaction] // Transaction context (if transaction_id provided)
|customerOpt: Option[Customer] // Customer context (if customer_id provided)
|```
|
|**Related Documentation:**
|- ABAC_Parameters_Summary - Complete list of all 18 parameters
|- ABAC_Object_Properties_Reference - Detailed property reference
|- ABAC_Testing_Examples - More testing examples
|""".stripMargin)
glossaryItems += GlossaryItem(
title = "ABAC_Parameters_Summary",
description =
s"""
|# ABAC Rule Parameters Summary
|
|The ABAC Rules Engine provides 18 parameters to your rule function, organized into three categories:
|
|## User Parameters (6 parameters)
|
|1. **authenticatedUser: User** - The logged-in user
|2. **authenticatedUserAttributes: List[UserAttributeTrait]** - Non-personal attributes of authenticated user (IsPersonal=false)
|3. **authenticatedUserAuthContext: List[UserAuthContext]** - Auth context of authenticated user
|4. **onBehalfOfUserOpt: Option[User]** - User being acted on behalf of (if provided)
|5. **onBehalfOfUserAttributes: List[UserAttributeTrait]** - Non-personal attributes of on-behalf-of user (IsPersonal=false)
|6. **onBehalfOfUserAuthContext: List[UserAuthContext]** - Auth context of on-behalf-of user
|
|## Target User Parameters (3 parameters)
|
|7. **userOpt: Option[User]** - Target user being evaluated
|8. **userAttributes: List[UserAttributeTrait]** - Non-personal attributes of target user (IsPersonal=false)
|9. **user: User** - Resolved target user (defaults to authenticatedUser)
|
|## Resource Context Parameters (9 parameters)
|
|10. **bankOpt: Option[Bank]** - Bank context (if bank_id provided)
|11. **bankAttributes: List[BankAttributeTrait]** - Bank attributes
|12. **accountOpt: Option[BankAccount]** - Account context (if account_id provided)
|13. **accountAttributes: List[AccountAttribute]** - Account attributes
|14. **transactionOpt: Option[Transaction]** - Transaction context (if transaction_id provided)
|15. **transactionAttributes: List[TransactionAttribute]** - Transaction attributes
|16. **transactionRequestOpt: Option[TransactionRequest]** - Transaction request context
|17. **transactionRequestAttributes: List[TransactionRequestAttributeTrait]** - Transaction request attributes
|18. **customerOpt: Option[Customer]** - Customer context (if customer_id provided)
|19. **customerAttributes: List[CustomerAttribute]** - Customer attributes
|
|## Usage in Rules
|
|```scala
|// Access user email
|authenticatedUser.emailAddress
|
|// Check if account exists and has sufficient balance
|accountOpt.exists(account => account.balance.toDouble >= 1000.0)
|
|// Check user attributes (non-personal only)
|authenticatedUserAttributes.exists(attr =>
| attr.name == "role" && attr.value == "admin"
|)
|
|// Note: Only non-personal attributes (IsPersonal=false) are included
|
|// Check delegation
|onBehalfOfUserOpt.isDefined
|```
|
|**Related Documentation:**
|- ABAC_Simple_Guide - Getting started guide
|- ABAC_Object_Properties_Reference - Detailed property reference
|""".stripMargin)
glossaryItems += GlossaryItem(
title = "ABAC_Object_Properties_Reference",
description =
s"""
|# ABAC Object Properties Reference
|
|This document lists all properties available on objects passed to ABAC rules.
|
|## User Object
|
|Available as: `authenticatedUser`, `user`, `onBehalfOfUserOpt.get`
|
|### Core Properties
|
|```scala
|user.userId // String - Unique user ID
|user.emailAddress // String - User's email
|user.name // String - Display name
|user.provider // String - Auth provider
|user.providerId // String - Provider's user ID
|```
|
|### Usage Examples
|
|```scala
|// Check if user is admin
|user.emailAddress.endsWith("@admin.com")
|
|// Check specific user
|user.userId == "alice@example.com"
|```
|
|## BankAccount Object
|
|Available as: `accountOpt.get`
|
|### Core Properties
|
|```scala
|account.accountId // AccountId - Account identifier
|account.bankId // BankId - Bank identifier
|account.accountType // String - Account type
|account.balance // BigDecimal - Current balance
|account.currency // String - Currency code (e.g., "EUR")
|account.name // String - Account name
|account.label // String - Account label
|account.owners // List[User] - Account owners
|```
|
|### Usage Examples
|
|```scala
|// Check balance
|accountOpt.exists(_.balance.toDouble >= 1000.0)
|
|// Check ownership
|accountOpt.exists(account =>
| account.owners.exists(owner => owner.userId == user.userId)
|)
|
|// Check currency
|accountOpt.exists(_.currency == "EUR")
|```
|
|## Bank Object
|
|Available as: `bankOpt.get`
|
|### Core Properties
|
|```scala
|bank.bankId // BankId - Bank identifier
|bank.shortName // String - Short name
|bank.fullName // String - Full legal name
|bank.logoUrl // String - URL to bank logo
|bank.websiteUrl // String - Bank website URL
|bank.bankRoutingScheme // String - Routing scheme
|bank.bankRoutingAddress // String - Routing address
|```
|
|### Usage Examples
|
|```scala
|// Check specific bank
|bankOpt.exists(_.bankId.value == "gh.29.uk")
|
|// Check bank by routing
|bankOpt.exists(_.bankRoutingScheme == "SWIFT_BIC")
|```
|
|## Transaction Object
|
|Available as: `transactionOpt.get`
|
|### Core Properties
|
|```scala
|transaction.id // TransactionId - Transaction ID
|transaction.amount // BigDecimal - Transaction amount
|transaction.currency // String - Currency code
|transaction.description // String - Description
|transaction.startDate // Option[Date] - Posted date
|transaction.finishDate // Option[Date] - Completed date
|transaction.transactionType // String - Transaction type
|```
|
|### Usage Examples
|
|```scala
|// Check transaction amount
|transactionOpt.exists(tx => tx.amount.abs.toDouble < 100.0)
|
|// Check transaction type
|transactionOpt.exists(_.transactionType == "SEPA")
|```
|
|## Customer Object
|
|Available as: `customerOpt.get`
|
|### Core Properties
|
|```scala
|customer.customerId // String - Customer ID
|customer.customerNumber // String - Customer number
|customer.legalName // String - Legal name
|customer.mobileNumber // String - Mobile number
|customer.email // String - Email address
|customer.dateOfBirth // Date - Date of birth
|```
|
|### Usage Examples
|
|```scala
|// Check customer email domain
|customerOpt.exists(_.email.endsWith("@company.com"))
|```
|
|## Attribute Objects
|
|### UserAttributeTrait
|
|```scala
|attr.name // String - Attribute name
|attr.value // String - Attribute value
|attr.attributeType // UserAttributeType - Type of attribute
|```
|
|### Usage Example
|
|```scala
|// Check for specific non-personal attribute
|authenticatedUserAttributes.exists(attr =>
| attr.name == "department" && attr.value == "finance"
|)
|
|// Note: User attributes in ABAC rules only include non-personal attributes
|// (where IsPersonal=false). Personal attributes are not available for
|// privacy and GDPR compliance reasons.
|```
|
|**Related Documentation:**
|- ABAC_Simple_Guide - Getting started guide
|- ABAC_Parameters_Summary - Complete parameter list
|""".stripMargin)
glossaryItems += GlossaryItem(
title = "ABAC_Testing_Examples",
description =
s"""
|# ABAC Testing Examples
|
|## API Endpoint
|
|```
|POST $getObpApiRoot/v6.0.0/management/abac-rules/{RULE_ID}/execute
|```
|
|## Example 1: Admin Only Rule
|
|**Rule Code:**
|```scala
|authenticatedUser.emailAddress.endsWith("@admin.com")
|```
|
|**Test Request:**
|```bash
|curl -X POST \\
| '$getObpApiRoot/v6.0.0/management/abac-rules/admin-only-rule/execute' \\
| -H 'Authorization: DirectLogin token=YOUR_TOKEN' \\
| -H 'Content-Type: application/json' \\
| -d '{}'
|```
|
|**Expected Result:**
|- Admin user `{"result": true}`
|- Regular user `{"result": false}`
|
|## Example 2: Account Owner Check
|
|**Rule Code:**
|```scala
|accountOpt.exists(account =>
| account.owners.exists(owner => owner.userId == user.userId)
|)
|```
|
|**Test Request:**
|```bash
|curl -X POST \\
| '$getObpApiRoot/v6.0.0/management/abac-rules/account-owner-only/execute' \\
| -H 'Authorization: DirectLogin token=YOUR_TOKEN' \\
| -H 'Content-Type: application/json' \\
| -d '{
| "user_id": "alice@example.com",
| "bank_id": "gh.29.uk",
| "account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"
| }'
|```
|
|## Example 3: Balance Check
|
|**Rule Code:**
|```scala
|accountOpt.exists(account => account.balance.toDouble >= 1000.0)
|```
|
|**Test Request:**
|```bash
|curl -X POST \\
| '$getObpApiRoot/v6.0.0/management/abac-rules/high-balance-only/execute' \\
| -H 'Authorization: DirectLogin token=YOUR_TOKEN' \\
| -H 'Content-Type: application/json' \\
| -d '{
| "bank_id": "gh.29.uk",
| "account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"
| }'
|```
|
|## Example 4: Transaction Amount Check
|
|**Rule Code:**
|```scala
|transactionOpt.exists(tx => tx.amount.abs.toDouble < 100.0)
|```
|
|**Test Request:**
|```bash
|curl -X POST \\
| '$getObpApiRoot/v6.0.0/management/abac-rules/small-transactions/execute' \\
| -H 'Authorization: DirectLogin token=YOUR_TOKEN' \\
| -H 'Content-Type: application/json' \\
| -d '{
| "bank_id": "gh.29.uk",
| "account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0",
| "transaction_id": "trans-123"
| }'
|```
|
|## Testing Patterns
|
|### Pattern 1: Test Different Users
|
|```bash
|# Test for admin
|curl -X POST '$getObpApiRoot/v6.0.0/management/abac-rules/RULE_ID/execute' \\
| -d '{"user_id": "admin@admin.com", "bank_id": "gh.29.uk"}'
|
|# Test for regular user
|curl -X POST '$getObpApiRoot/v6.0.0/management/abac-rules/RULE_ID/execute' \\
| -d '{"user_id": "alice@example.com", "bank_id": "gh.29.uk"}'
|```
|
|### Pattern 2: Test Edge Cases
|
|```bash
|# No context (minimal)
|curl -X POST '$getObpApiRoot/v6.0.0/management/abac-rules/RULE_ID/execute' -d '{}'
|
|# Full context
|curl -X POST '$getObpApiRoot/v6.0.0/management/abac-rules/RULE_ID/execute' -d '{
| "user_id": "alice@example.com",
| "bank_id": "gh.29.uk",
| "account_id": "8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0",
| "transaction_id": "trans-123",
| "customer_id": "cust-456"
|}'
|```
|
|## Common Errors
|
|### Error 1: Rule Not Found
|
|```bash
|curl -X POST '$getObpApiRoot/v6.0.0/management/abac-rules/nonexistent-rule/execute' \\
| -H 'Authorization: DirectLogin token=YOUR_TOKEN' \\
| -d '{}'
|```
|
|**Response:** `{"error": "ABAC Rule not found with ID: nonexistent-rule"}`
|
|### Error 2: Invalid Context
|
|**Response:** Objects will be `None` if IDs are invalid, rule should handle gracefully
|
|**Related Documentation:**
|- ABAC_Simple_Guide - Getting started guide
|- ABAC_Parameters_Summary - Complete parameter list
|- ABAC_Object_Properties_Reference - Property reference
|""".stripMargin)
private def getContentFromMarkdownFile(path: String): String = {
val source = scala.io.Source.fromFile(path)
val lines: String = try source.mkString finally source.close()

View File

@ -1974,7 +1974,7 @@ trait APIMethods310 {
InvalidJsonFormat,
UnknownError
),
List(apiTagProduct),
List(apiTagProduct, apiTagProductAttribute, apiTagAttribute),
Some(List(canCreateProductAttribute))
)
@ -2033,7 +2033,7 @@ trait APIMethods310 {
UserHasMissingRoles,
UnknownError
),
List(apiTagProduct),
List(apiTagProduct, apiTagProductAttribute, apiTagAttribute),
Some(List(canGetProductAttribute))
)
@ -2075,7 +2075,7 @@ trait APIMethods310 {
UserHasMissingRoles,
UnknownError
),
List(apiTagProduct),
List(apiTagProduct, apiTagProductAttribute, apiTagAttribute),
Some(List(canUpdateProductAttribute))
)
@ -2135,7 +2135,7 @@ trait APIMethods310 {
BankNotFound,
UnknownError
),
List(apiTagProduct),
List(apiTagProduct, apiTagProductAttribute, apiTagAttribute),
Some(List(canUpdateProductAttribute)))
lazy val deleteProductAttribute : OBPEndpoint = {
@ -2667,7 +2667,7 @@ trait APIMethods310 {
InvalidJsonFormat,
UnknownError
),
List(apiTagAccount),
List(apiTagAccount, apiTagAccountAttribute, apiTagAttribute),
Some(List(canCreateAccountAttributeAtOneBank))
)
@ -2740,7 +2740,7 @@ trait APIMethods310 {
InvalidJsonFormat,
UnknownError
),
List(apiTagAccount),
List(apiTagAccount, apiTagAccountAttribute, apiTagAttribute),
Some(List(canUpdateAccountAttribute))
)
@ -5147,7 +5147,7 @@ trait APIMethods310 {
InvalidJsonFormat,
UnknownError
),
List(apiTagCard))
List(apiTagCard, apiTagCardAttribute, apiTagAttribute))
lazy val createCardAttribute : OBPEndpoint = {
case "management"::"banks" :: bankId :: "cards" :: cardId :: "attribute" :: Nil JsonPost json -> _=> {
@ -5218,7 +5218,7 @@ trait APIMethods310 {
InvalidJsonFormat,
UnknownError
),
List(apiTagCard))
List(apiTagCard, apiTagCardAttribute, apiTagAttribute))
lazy val updateCardAttribute : OBPEndpoint = {
case "management"::"banks" :: bankId :: "cards" :: cardId :: "attributes" :: cardAttributeId :: Nil JsonPut json -> _=> {

View File

@ -1672,7 +1672,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagTransactionRequest),
List(apiTagTransactionRequest, apiTagTransactionRequestAttribute, apiTagAttribute),
Some(List(canCreateTransactionRequestAttributeAtOneBank))
)
@ -1744,7 +1744,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagTransactionRequest),
List(apiTagTransactionRequest, apiTagTransactionRequestAttribute, apiTagAttribute),
Some(List(canGetTransactionRequestAttributeAtOneBank))
)
@ -1798,7 +1798,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagTransactionRequest),
List(apiTagTransactionRequest, apiTagTransactionRequestAttribute, apiTagAttribute),
Some(List(canGetTransactionRequestAttributesAtOneBank))
)
@ -1852,7 +1852,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagTransactionRequest),
List(apiTagTransactionRequest, apiTagTransactionRequestAttribute, apiTagAttribute),
Some(List(canUpdateTransactionRequestAttributeAtOneBank))
)
@ -1935,7 +1935,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagTransactionRequest),
List(apiTagTransactionRequest, apiTagTransactionRequestAttribute, apiTagAttribute),
Some(List(canCreateTransactionRequestAttributeDefinitionAtOneBank))
)
@ -2011,7 +2011,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagTransactionRequest),
List(apiTagTransactionRequest, apiTagTransactionRequestAttribute, apiTagAttribute),
Some(List(canGetTransactionRequestAttributeDefinitionAtOneBank))
)
@ -2058,7 +2058,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagTransactionRequest),
List(apiTagTransactionRequest, apiTagTransactionRequestAttribute, apiTagAttribute),
Some(List(canDeleteTransactionRequestAttributeDefinitionAtOneBank))
)
@ -6106,7 +6106,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagCustomer),
List(apiTagCustomer, apiTagCustomerAttribute, apiTagAttribute),
Some(
List(
canCreateCustomerAttributeAtOneBank,
@ -6186,7 +6186,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagCustomer),
List(apiTagCustomer, apiTagCustomerAttribute, apiTagAttribute),
Some(
List(
canUpdateCustomerAttributeAtOneBank,
@ -6270,7 +6270,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagCustomer),
List(apiTagCustomer, apiTagCustomerAttribute, apiTagAttribute),
Some(
List(
canGetCustomerAttributesAtOneBank,
@ -6327,7 +6327,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagCustomer),
List(apiTagCustomer, apiTagCustomerAttribute, apiTagAttribute),
Some(
List(canGetCustomerAttributeAtOneBank, canGetCustomerAttributeAtAnyBank)
)
@ -6446,7 +6446,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagTransaction),
List(apiTagTransaction, apiTagTransactionAttribute, apiTagAttribute),
Some(List(canCreateTransactionAttributeAtOneBank))
)
@ -6519,7 +6519,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagTransaction),
List(apiTagTransaction, apiTagTransactionAttribute, apiTagAttribute),
Some(List(canUpdateTransactionAttributeAtOneBank))
)
@ -6598,7 +6598,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagTransaction),
List(apiTagTransaction, apiTagTransactionAttribute, apiTagAttribute),
Some(List(canGetTransactionAttributesAtOneBank))
)
@ -6652,7 +6652,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagTransaction),
List(apiTagTransaction, apiTagTransactionAttribute, apiTagAttribute),
Some(List(canGetTransactionAttributeAtOneBank))
)
@ -7346,7 +7346,7 @@ trait APIMethods400 extends MdcLoggable {
UserHasMissingRoles,
UnknownError
),
List(apiTagCustomer),
List(apiTagCustomer, apiTagCustomerAttribute, apiTagAttribute),
Some(
List(
canDeleteCustomerAttributeAtOneBank,
@ -7908,7 +7908,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagCustomer),
List(apiTagCustomer, apiTagCustomerAttribute, apiTagAttribute),
Some(List(canCreateCustomerAttributeDefinitionAtOneBank))
)
@ -7988,7 +7988,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagAccount),
List(apiTagAccount, apiTagAccountAttribute, apiTagAttribute),
Some(List(canCreateAccountAttributeDefinitionAtOneBank))
)
@ -8068,7 +8068,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagProduct),
List(apiTagProduct, apiTagProductAttribute, apiTagAttribute),
Some(List(canCreateProductAttributeDefinitionAtOneBank))
)
@ -8171,7 +8171,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagProduct),
List(apiTagProduct, apiTagProductAttribute, apiTagAttribute),
Some(List(canCreateProductAttribute))
)
@ -8253,7 +8253,7 @@ trait APIMethods400 extends MdcLoggable {
UserHasMissingRoles,
UnknownError
),
List(apiTagProduct),
List(apiTagProduct, apiTagProductAttribute, apiTagAttribute),
Some(List(canUpdateProductAttribute))
)
@ -8333,7 +8333,7 @@ trait APIMethods400 extends MdcLoggable {
UserHasMissingRoles,
UnknownError
),
List(apiTagProduct),
List(apiTagProduct, apiTagProductAttribute, apiTagAttribute),
Some(List(canUpdateProductAttribute))
)
@ -8645,7 +8645,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagBank),
List(apiTagBank, apiTagBankAttribute, apiTagAttribute),
Some(List(canCreateBankAttributeDefinitionAtOneBank))
)
@ -8737,7 +8737,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagBank),
List(apiTagBank, apiTagBankAttribute, apiTagAttribute),
Some(List(canCreateBankAttribute))
)
@ -8800,7 +8800,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagBank),
List(apiTagBank, apiTagBankAttribute, apiTagAttribute),
Some(List(canGetBankAttribute))
)
@ -8836,7 +8836,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagBank),
List(apiTagBank, apiTagBankAttribute, apiTagAttribute),
Some(List(canGetBankAttribute))
)
@ -8876,7 +8876,7 @@ trait APIMethods400 extends MdcLoggable {
UserHasMissingRoles,
UnknownError
),
List(apiTagBank)
List(apiTagBank, apiTagBankAttribute, apiTagAttribute)
)
lazy val updateBankAttribute: OBPEndpoint = {
@ -8953,7 +8953,7 @@ trait APIMethods400 extends MdcLoggable {
BankNotFound,
UnknownError
),
List(apiTagBank)
List(apiTagBank, apiTagBankAttribute, apiTagAttribute)
)
lazy val deleteBankAttribute: OBPEndpoint = {
@ -9004,7 +9004,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagTransaction),
List(apiTagTransaction, apiTagTransactionAttribute, apiTagAttribute),
Some(List(canCreateTransactionAttributeDefinitionAtOneBank))
)
@ -9084,7 +9084,7 @@ trait APIMethods400 extends MdcLoggable {
InvalidJsonFormat,
UnknownError
),
List(apiTagCard),
List(apiTagCard, apiTagCardAttribute, apiTagAttribute),
Some(List(canCreateCardAttributeDefinitionAtOneBank))
)
@ -9159,7 +9159,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagTransaction),
List(apiTagTransaction, apiTagTransactionAttribute, apiTagAttribute),
Some(List(canDeleteTransactionAttributeDefinitionAtOneBank))
)
@ -9202,7 +9202,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagCustomer),
List(apiTagCustomer, apiTagCustomerAttribute, apiTagAttribute),
Some(List(canDeleteCustomerAttributeDefinitionAtOneBank))
)
@ -9243,7 +9243,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagAccount),
List(apiTagAccount, apiTagAccountAttribute, apiTagAttribute),
Some(List(canDeleteAccountAttributeDefinitionAtOneBank))
)
@ -9284,7 +9284,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagProduct),
List(apiTagProduct, apiTagProductAttribute, apiTagAttribute),
Some(List(canDeleteProductAttributeDefinitionAtOneBank))
)
@ -9325,7 +9325,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagCard),
List(apiTagCard, apiTagCardAttribute, apiTagAttribute),
Some(List(canDeleteCardAttributeDefinitionAtOneBank))
)
@ -9366,7 +9366,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagProduct),
List(apiTagProduct, apiTagProductAttribute, apiTagAttribute),
Some(List(canGetProductAttributeDefinitionAtOneBank))
)
@ -9408,7 +9408,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagCustomer),
List(apiTagCustomer, apiTagCustomerAttribute, apiTagAttribute),
Some(List(canGetCustomerAttributeDefinitionAtOneBank))
)
@ -9450,7 +9450,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagAccount),
List(apiTagAccount, apiTagAccountAttribute, apiTagAttribute),
Some(List(canGetAccountAttributeDefinitionAtOneBank))
)
@ -9492,7 +9492,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagTransaction),
List(apiTagTransaction, apiTagTransactionAttribute, apiTagAttribute),
Some(List(canGetTransactionAttributeDefinitionAtOneBank))
)
@ -9539,7 +9539,7 @@ trait APIMethods400 extends MdcLoggable {
$BankNotFound,
UnknownError
),
List(apiTagCard),
List(apiTagCard, apiTagCardAttribute, apiTagAttribute),
Some(List(canGetCardAttributeDefinitionAtOneBank))
)

View File

@ -1181,7 +1181,7 @@ trait APIMethods510 {
InvalidJsonFormat,
UnknownError
),
List(apiTagATM),
List(apiTagATM, apiTagAtmAttribute, apiTagAttribute),
Some(List(canCreateAtmAttribute, canCreateAtmAttributeAtAnyBank))
)
@ -1269,7 +1269,7 @@ trait APIMethods510 {
InvalidJsonFormat,
UnknownError
),
List(apiTagATM),
List(apiTagATM, apiTagAtmAttribute, apiTagAttribute),
Some(List(canGetAtmAttribute, canGetAtmAttributeAtAnyBank))
)
@ -1305,7 +1305,7 @@ trait APIMethods510 {
InvalidJsonFormat,
UnknownError
),
List(apiTagATM),
List(apiTagATM, apiTagAtmAttribute, apiTagAttribute),
Some(List(canGetAtmAttribute, canGetAtmAttributeAtAnyBank))
)
@ -1344,7 +1344,7 @@ trait APIMethods510 {
UserHasMissingRoles,
UnknownError
),
List(apiTagATM),
List(apiTagATM, apiTagAtmAttribute, apiTagAttribute),
Some(List(canUpdateAtmAttribute, canUpdateAtmAttributeAtAnyBank))
)
@ -1402,7 +1402,7 @@ trait APIMethods510 {
UserHasMissingRoles,
UnknownError
),
List(apiTagATM),
List(apiTagATM, apiTagAtmAttribute, apiTagAttribute),
Some(List(canDeleteAtmAttribute, canDeleteAtmAttributeAtAnyBank))
)

View File

@ -31,7 +31,7 @@ import code.api.v6_0_0.{AbacRuleJsonV600, AbacRuleResultJsonV600, AbacRulesJsonV
import code.api.v6_0_0.OBPAPI6_0_0
import code.abacrule.{AbacRuleEngine, MappedAbacRuleProvider}
import code.metrics.APIMetrics
import code.bankconnectors.LocalMappedConnectorInternal
import code.bankconnectors.{Connector, LocalMappedConnectorInternal}
import code.bankconnectors.LocalMappedConnectorInternal._
import code.entitlement.Entitlement
import code.loginattempts.LoginAttempt
@ -49,6 +49,7 @@ import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.{CustomerAttribute, _}
import com.openbankproject.commons.model.enums.DynamicEntityOperation._
import com.openbankproject.commons.model.enums.UserAttributeType
import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion}
import net.liftweb.common.{Empty, Failure, Full}
import org.apache.commons.lang3.StringUtils
@ -4153,10 +4154,10 @@ trait APIMethods600 {
|ABAC rules are Scala functions that return a Boolean value indicating whether access should be granted.
|
|**Documentation:**
|- [ABAC Simple Guide](glossary#ABAC_Simple_Guide.md) - Getting started with ABAC rules
|- [ABAC Parameters Summary](glossary#ABAC_Parameters_Summary.md) - Complete list of all 18 parameters
|- [ABAC Object Properties Reference](glossary#ABAC_Object_Properties_Reference.md) - Detailed property reference
|- [ABAC Testing Examples](glossary#ABAC_Testing_Examples.md) - Testing examples and patterns
|- ${Glossary.getGlossaryItemLink("ABAC_Simple_Guide")} - Getting started with ABAC rules
|- ${Glossary.getGlossaryItemLink("ABAC_Parameters_Summary")} - Complete list of all 18 parameters
|- ${Glossary.getGlossaryItemLink("ABAC_Object_Properties_Reference")} - Detailed property reference
|- ${Glossary.getGlossaryItemLink("ABAC_Testing_Examples")} - Testing examples and patterns
|
|The rule function receives 18 parameters including authenticatedUser, attributes, auth context, and optional objects (bank, account, transaction, etc.).
|
@ -4247,9 +4248,9 @@ trait APIMethods600 {
s"""Get an ABAC rule by its ID.
|
|**Documentation:**
|- [ABAC Simple Guide](glossary#ABAC_Simple_Guide.md) - Getting started with ABAC rules
|- [ABAC Parameters Summary](glossary#ABAC_Parameters_Summary.md) - Complete list of all 18 parameters
|- [ABAC Object Properties Reference](glossary#ABAC_Object_Properties_Reference.md) - Detailed property reference
|- ${Glossary.getGlossaryItemLink("ABAC_Simple_Guide")} - Getting started with ABAC rules
|- ${Glossary.getGlossaryItemLink("ABAC_Parameters_Summary")} - Complete list of all 18 parameters
|- ${Glossary.getGlossaryItemLink("ABAC_Object_Properties_Reference")} - Detailed property reference
|
|${userAuthenticationMessage(true)}
|
@ -4300,9 +4301,9 @@ trait APIMethods600 {
s"""Get all ABAC rules.
|
|**Documentation:**
|- [ABAC Simple Guide](glossary#ABAC_Simple_Guide.md) - Getting started with ABAC rules
|- [ABAC Parameters Summary](glossary#ABAC_Parameters_Summary.md) - Complete list of all 18 parameters
|- [ABAC Object Properties Reference](glossary#ABAC_Object_Properties_Reference.md) - Detailed property reference
|- ${Glossary.getGlossaryItemLink("ABAC_Simple_Guide")} - Getting started with ABAC rules
|- ${Glossary.getGlossaryItemLink("ABAC_Parameters_Summary")} - Complete list of all 18 parameters
|- ${Glossary.getGlossaryItemLink("ABAC_Object_Properties_Reference")} - Detailed property reference
|
|${userAuthenticationMessage(true)}
|
@ -4355,9 +4356,9 @@ trait APIMethods600 {
s"""Update an existing ABAC rule.
|
|**Documentation:**
|- [ABAC Simple Guide](glossary#ABAC_Simple_Guide.md) - Getting started with ABAC rules
|- [ABAC Parameters Summary](glossary#ABAC_Parameters_Summary.md) - Complete list of all 18 parameters
|- [ABAC Object Properties Reference](glossary#ABAC_Object_Properties_Reference.md) - Detailed property reference
|- ${Glossary.getGlossaryItemLink("ABAC_Simple_Guide")} - Getting started with ABAC rules
|- ${Glossary.getGlossaryItemLink("ABAC_Parameters_Summary")} - Complete list of all 18 parameters
|- ${Glossary.getGlossaryItemLink("ABAC_Object_Properties_Reference")} - Detailed property reference
|
|${userAuthenticationMessage(true)}
|
@ -4434,8 +4435,8 @@ trait APIMethods600 {
s"""Delete an ABAC rule by its ID.
|
|**Documentation:**
|- [ABAC Simple Guide](glossary#ABAC_Simple_Guide.md) - Getting started with ABAC rules
|- [ABAC Parameters Summary](glossary#ABAC_Parameters_Summary.md) - Complete list of all 18 parameters
|- ${Glossary.getGlossaryItemLink("ABAC_Simple_Guide")} - Getting started with ABAC rules
|- ${Glossary.getGlossaryItemLink("ABAC_Parameters_Summary")} - Complete list of all 18 parameters
|
|${userAuthenticationMessage(true)}
|
@ -4472,6 +4473,333 @@ trait APIMethods600 {
}
}
staticResourceDocs += ResourceDoc(
getAbacRuleSchema,
implementedInApiVersion,
nameOf(getAbacRuleSchema),
"GET",
"/management/abac-rules-schema",
"Get ABAC Rule Schema",
s"""Get schema information about ABAC rule structure for building rule code.
|
|This endpoint returns schema information including:
|- All 18 parameters available in ABAC rules
|- Object types (User, Bank, Account, etc.) and their properties
|- Available operators and syntax
|- Example rules
|
|This schema information is useful for:
|- Building rule editors with auto-completion
|- Validating rule syntax in frontends
|- AI agents that help construct rules
|- Dynamic form builders
|
|${userAuthenticationMessage(true)}
|
|""".stripMargin,
EmptyBody,
AbacRuleSchemaJsonV600(
parameters = List(
AbacParameterJsonV600(
name = "authenticatedUser",
`type` = "User",
description = "The logged-in user (always present)",
required = true,
category = "User"
)
),
object_types = List(
AbacObjectTypeJsonV600(
name = "User",
description = "User object with profile information",
properties = List(
AbacObjectPropertyJsonV600(
name = "userId",
`type` = "String",
description = "Unique user ID"
)
)
)
),
examples = List(
"authenticatedUser.userId == user.userId",
"bankOpt.isDefined && bankOpt.get.bankId.value == \"gh.29.uk\""
),
available_operators = List("==", "!=", "&&", "||", "!", ">", "<", ">=", "<=", "contains", "isDefined"),
notes = List(
"Only authenticatedUser is guaranteed to exist (not wrapped in Option)",
"All other objects are Option types - use isDefined or pattern matching",
"Attributes are Lists - use .find(), .exists(), .forall() etc."
)
),
List(
UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTagABAC),
Some(List(canGetAbacRule))
)
lazy val getAbacRuleSchema: OBPEndpoint = {
case "management" :: "abac-rules-schema" :: Nil JsonGet _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(user), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", user.userId, canGetAbacRule, callContext)
} yield {
val metadata = AbacRuleSchemaJsonV600(
parameters = List(
AbacParameterJsonV600("authenticatedUser", "User", "The logged-in user (always present)", required = true, "User"),
AbacParameterJsonV600("authenticatedUserAttributes", "List[UserAttributeTrait]", "Non-personal attributes of authenticated user", required = true, "User"),
AbacParameterJsonV600("authenticatedUserAuthContext", "List[UserAuthContext]", "Auth context of authenticated user", required = true, "User"),
AbacParameterJsonV600("onBehalfOfUserOpt", "Option[User]", "User being acted on behalf of (delegation)", required = false, "User"),
AbacParameterJsonV600("onBehalfOfUserAttributes", "List[UserAttributeTrait]", "Attributes of delegation user", required = false, "User"),
AbacParameterJsonV600("onBehalfOfUserAuthContext", "List[UserAuthContext]", "Auth context of delegation user", required = false, "User"),
AbacParameterJsonV600("userOpt", "Option[User]", "Target user being evaluated", required = false, "User"),
AbacParameterJsonV600("userAttributes", "List[UserAttributeTrait]", "Attributes of target user", required = false, "User"),
AbacParameterJsonV600("bankOpt", "Option[Bank]", "Bank context", required = false, "Bank"),
AbacParameterJsonV600("bankAttributes", "List[BankAttributeTrait]", "Bank attributes", required = false, "Bank"),
AbacParameterJsonV600("accountOpt", "Option[BankAccount]", "Account context", required = false, "Account"),
AbacParameterJsonV600("accountAttributes", "List[AccountAttribute]", "Account attributes", required = false, "Account"),
AbacParameterJsonV600("transactionOpt", "Option[Transaction]", "Transaction context", required = false, "Transaction"),
AbacParameterJsonV600("transactionAttributes", "List[TransactionAttribute]", "Transaction attributes", required = false, "Transaction"),
AbacParameterJsonV600("transactionRequestOpt", "Option[TransactionRequest]", "Transaction request context", required = false, "TransactionRequest"),
AbacParameterJsonV600("transactionRequestAttributes", "List[TransactionRequestAttributeTrait]", "Transaction request attributes", required = false, "TransactionRequest"),
AbacParameterJsonV600("customerOpt", "Option[Customer]", "Customer context", required = false, "Customer"),
AbacParameterJsonV600("customerAttributes", "List[CustomerAttribute]", "Customer attributes", required = false, "Customer")
),
object_types = List(
AbacObjectTypeJsonV600("User", "User object with profile and authentication information", List(
AbacObjectPropertyJsonV600("userId", "String", "Unique user ID"),
AbacObjectPropertyJsonV600("emailAddress", "String", "User email address"),
AbacObjectPropertyJsonV600("provider", "String", "Authentication provider (e.g., 'obp')"),
AbacObjectPropertyJsonV600("name", "String", "User display name"),
AbacObjectPropertyJsonV600("idGivenByProvider", "String", "ID given by provider (same as username)"),
AbacObjectPropertyJsonV600("createdByConsentId", "Option[String]", "Consent ID that created the user (if any)"),
AbacObjectPropertyJsonV600("isDeleted", "Option[Boolean]", "Whether user is deleted")
)),
AbacObjectTypeJsonV600("Bank", "Bank object", List(
AbacObjectPropertyJsonV600("bankId", "BankId", "Bank ID"),
AbacObjectPropertyJsonV600("fullName", "String", "Bank full name"),
AbacObjectPropertyJsonV600("shortName", "String", "Bank short name"),
AbacObjectPropertyJsonV600("logoUrl", "String", "Bank logo URL"),
AbacObjectPropertyJsonV600("websiteUrl", "String", "Bank website URL"),
AbacObjectPropertyJsonV600("bankRoutingScheme", "String", "Bank routing scheme"),
AbacObjectPropertyJsonV600("bankRoutingAddress", "String", "Bank routing address")
)),
AbacObjectTypeJsonV600("BankAccount", "Bank account object", List(
AbacObjectPropertyJsonV600("accountId", "AccountId", "Account ID"),
AbacObjectPropertyJsonV600("bankId", "BankId", "Bank ID"),
AbacObjectPropertyJsonV600("accountType", "String", "Account type"),
AbacObjectPropertyJsonV600("balance", "BigDecimal", "Account balance"),
AbacObjectPropertyJsonV600("currency", "String", "Account currency"),
AbacObjectPropertyJsonV600("name", "String", "Account name"),
AbacObjectPropertyJsonV600("label", "String", "Account label"),
AbacObjectPropertyJsonV600("number", "String", "Account number"),
AbacObjectPropertyJsonV600("lastUpdate", "Date", "Last update date"),
AbacObjectPropertyJsonV600("branchId", "String", "Branch ID"),
AbacObjectPropertyJsonV600("accountRoutings", "List[AccountRouting]", "Account routings")
)),
AbacObjectTypeJsonV600("Transaction", "Transaction object", List(
AbacObjectPropertyJsonV600("id", "TransactionId", "Transaction ID"),
AbacObjectPropertyJsonV600("uuid", "String", "Universally unique ID"),
AbacObjectPropertyJsonV600("thisAccount", "BankAccount", "This account"),
AbacObjectPropertyJsonV600("otherAccount", "Counterparty", "Other account/counterparty"),
AbacObjectPropertyJsonV600("transactionType", "String", "Transaction type (e.g., cash withdrawal)"),
AbacObjectPropertyJsonV600("amount", "BigDecimal", "Transaction amount"),
AbacObjectPropertyJsonV600("currency", "String", "Transaction currency (ISO 4217)"),
AbacObjectPropertyJsonV600("description", "Option[String]", "Bank provided label"),
AbacObjectPropertyJsonV600("startDate", "Date", "Date transaction was initiated"),
AbacObjectPropertyJsonV600("finishDate", "Option[Date]", "Date money finished changing hands"),
AbacObjectPropertyJsonV600("balance", "BigDecimal", "New balance after transaction"),
AbacObjectPropertyJsonV600("status", "Option[String]", "Transaction status")
)),
AbacObjectTypeJsonV600("TransactionRequest", "Transaction request object", List(
AbacObjectPropertyJsonV600("id", "TransactionRequestId", "Transaction request ID"),
AbacObjectPropertyJsonV600("type", "String", "Transaction request type"),
AbacObjectPropertyJsonV600("from", "TransactionRequestAccount", "From account"),
AbacObjectPropertyJsonV600("status", "String", "Transaction request status"),
AbacObjectPropertyJsonV600("start_date", "Date", "Start date"),
AbacObjectPropertyJsonV600("end_date", "Date", "End date"),
AbacObjectPropertyJsonV600("transaction_ids", "String", "Associated transaction IDs"),
AbacObjectPropertyJsonV600("charge", "TransactionRequestCharge", "Charge information"),
AbacObjectPropertyJsonV600("this_bank_id", "BankId", "This bank ID"),
AbacObjectPropertyJsonV600("this_account_id", "AccountId", "This account ID"),
AbacObjectPropertyJsonV600("counterparty_id", "CounterpartyId", "Counterparty ID")
)),
AbacObjectTypeJsonV600("Customer", "Customer object", List(
AbacObjectPropertyJsonV600("customerId", "String", "Customer ID (UUID)"),
AbacObjectPropertyJsonV600("bankId", "String", "Bank ID"),
AbacObjectPropertyJsonV600("number", "String", "Customer number (bank identifier)"),
AbacObjectPropertyJsonV600("legalName", "String", "Customer legal name"),
AbacObjectPropertyJsonV600("mobileNumber", "String", "Customer mobile number"),
AbacObjectPropertyJsonV600("email", "String", "Customer email"),
AbacObjectPropertyJsonV600("dateOfBirth", "Date", "Date of birth"),
AbacObjectPropertyJsonV600("relationshipStatus", "String", "Relationship status"),
AbacObjectPropertyJsonV600("dependents", "Integer", "Number of dependents")
)),
AbacObjectTypeJsonV600("UserAttributeTrait", "User attribute", List(
AbacObjectPropertyJsonV600("name", "String", "Attribute name"),
AbacObjectPropertyJsonV600("value", "String", "Attribute value"),
AbacObjectPropertyJsonV600("attributeType", "AttributeType", "Attribute type (STRING, INTEGER, DOUBLE, DATE_WITH_DAY)")
)),
AbacObjectTypeJsonV600("AccountAttribute", "Account attribute", List(
AbacObjectPropertyJsonV600("name", "String", "Attribute name"),
AbacObjectPropertyJsonV600("value", "String", "Attribute value"),
AbacObjectPropertyJsonV600("attributeType", "AttributeType", "Attribute type")
)),
AbacObjectTypeJsonV600("TransactionAttribute", "Transaction attribute", List(
AbacObjectPropertyJsonV600("name", "String", "Attribute name"),
AbacObjectPropertyJsonV600("value", "String", "Attribute value"),
AbacObjectPropertyJsonV600("attributeType", "AttributeType", "Attribute type")
)),
AbacObjectTypeJsonV600("CustomerAttribute", "Customer attribute", List(
AbacObjectPropertyJsonV600("name", "String", "Attribute name"),
AbacObjectPropertyJsonV600("value", "String", "Attribute value"),
AbacObjectPropertyJsonV600("attributeType", "AttributeType", "Attribute type")
))
),
examples = List(
"// Check if authenticated user matches target user",
"authenticatedUser.userId == userOpt.get.userId",
"// Check user email contains admin",
"authenticatedUser.emailAddress.contains(\"admin\")",
"// Check specific bank",
"bankOpt.isDefined && bankOpt.get.bankId.value == \"gh.29.uk\"",
"// Check account balance",
"accountOpt.isDefined && accountOpt.get.balance > 1000",
"// Check user attributes",
"userAttributes.exists(attr => attr.name == \"account_type\" && attr.value == \"premium\")",
"// Check authenticated user has role attribute",
"authenticatedUserAttributes.find(_.name == \"role\").exists(_.value == \"admin\")",
"// IMPORTANT: Use camelCase (userId NOT user_id)",
"// IMPORTANT: Parameters are: authenticatedUser, userOpt, accountOpt (with Opt suffix for Optional)",
"// IMPORTANT: Check isDefined before using .get on Option types"
),
available_operators = List(
"==", "!=", "&&", "||", "!", ">", "<", ">=", "<=",
"contains", "startsWith", "endsWith",
"isDefined", "isEmpty", "nonEmpty",
"exists", "forall", "find", "filter",
"get", "getOrElse"
),
notes = List(
"PARAMETER NAMES: Use authenticatedUser, userOpt, accountOpt, bankOpt, transactionOpt, etc. (NOT user, account, bank)",
"PROPERTY NAMES: Use camelCase - userId (NOT user_id), accountId (NOT account_id), emailAddress (NOT email_address)",
"OPTION TYPES: Only authenticatedUser is guaranteed to exist. All others are Option types - check isDefined before using .get",
"ATTRIBUTES: All attributes are Lists - use Scala collection methods like exists(), find(), filter()",
"SAFE OPTION HANDLING: Use pattern matching: userOpt match { case Some(u) => u.userId == ... case None => false }",
"RETURN TYPE: Rule must return Boolean - true = access granted, false = access denied",
"AUTO-FETCHING: Objects are automatically fetched based on IDs passed to execute endpoint",
"COMMON MISTAKE: Writing 'user.user_id' instead of 'userOpt.get.userId' or 'authenticatedUser.userId'"
)
)
(metadata, HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
validateAbacRule,
implementedInApiVersion,
nameOf(validateAbacRule),
"POST",
"/management/abac-rules/validate",
"Validate ABAC Rule",
s"""Validate ABAC rule code syntax and structure without creating or executing the rule.
|
|This endpoint performs the following validations:
|- Parse the rule_code as a Scala expression
|- Validate syntax - check for parsing errors
|- Validate field references - check if referenced objects/fields exist
|- Check type consistency - verify the expression returns a Boolean
|
|**Available ABAC Context Objects:**
|- AuthenticatedUser - The user who is logged in
|- OnBehalfOfUser - Optional delegation user
|- User - Target user being evaluated
|- Bank, Account, View, Transaction, TransactionRequest, Customer
|- Attributes for each entity (e.g., userAttributes, accountAttributes)
|
|**Documentation:**
|- ${Glossary.getGlossaryItemLink("ABAC_Simple_Guide")} - Getting started with ABAC rules
|- ${Glossary.getGlossaryItemLink("ABAC_Parameters_Summary")} - Complete list of all 18 parameters
|- ${Glossary.getGlossaryItemLink("ABAC_Object_Properties_Reference")} - Detailed property reference
|
|This is a "dry-run" validation that does NOT save or execute the rule.
|
|${userAuthenticationMessage(true)}
|
|""".stripMargin,
ValidateAbacRuleJsonV600(
rule_code = """AuthenticatedUser.user_id == Account.owner_id"""
),
ValidateAbacRuleSuccessJsonV600(
valid = true,
message = "ABAC rule code is valid"
),
List(
UserNotLoggedIn,
UserHasMissingRoles,
InvalidJsonFormat,
UnknownError
),
List(apiTagABAC),
Some(List(canCreateAbacRule))
)
lazy val validateAbacRule: OBPEndpoint = {
case "management" :: "abac-rules" :: "validate" :: Nil JsonPost json -> _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(user), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", user.userId, canCreateAbacRule, callContext)
validateJson <- NewStyle.function.tryons(s"$InvalidJsonFormat", 400, callContext) {
json.extract[ValidateAbacRuleJsonV600]
}
_ <- NewStyle.function.tryons(s"Rule code must not be empty", 400, callContext) {
validateJson.rule_code.trim.nonEmpty
}
validationResult <- Future {
AbacRuleEngine.validateRuleCode(validateJson.rule_code) match {
case Full(msg) =>
Full(ValidateAbacRuleSuccessJsonV600(
valid = true,
message = msg
))
case Failure(errorMsg, _, _) =>
// Extract error details from the error message
val cleanError = errorMsg.replace("Invalid ABAC rule code: ", "").replace("Failed to compile ABAC rule: ", "")
Full(ValidateAbacRuleFailureJsonV600(
valid = false,
error = cleanError,
message = "Rule validation failed",
details = ValidateAbacRuleErrorDetailsJsonV600(
error_type = if (cleanError.toLowerCase.contains("syntax")) "SyntaxError"
else if (cleanError.toLowerCase.contains("type")) "TypeError"
else "CompilationError"
)
))
case Empty =>
Full(ValidateAbacRuleFailureJsonV600(
valid = false,
error = "Unknown validation error",
message = "Rule validation failed",
details = ValidateAbacRuleErrorDetailsJsonV600(
error_type = "UnknownError"
)
))
}
} map {
unboxFullOrFail(_, callContext, "Validation failed", 400)
}
} yield {
(validationResult, HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
executeAbacRule,
implementedInApiVersion,
@ -4484,10 +4812,10 @@ trait APIMethods600 {
|This endpoint allows you to test an ABAC rule with specific context (authenticated user, bank, account, transaction, customer, etc.).
|
|**Documentation:**
|- [ABAC Simple Guide](glossary#ABAC_Simple_Guide.md) - Getting started with ABAC rules
|- [ABAC Parameters Summary](glossary#ABAC_Parameters_Summary.md) - Complete list of all 18 parameters
|- [ABAC Object Properties Reference](glossary#ABAC_Object_Properties_Reference.md) - Detailed property reference
|- [ABAC Testing Examples](glossary#ABAC_Testing_Examples.md) - Testing examples and patterns
|- ${Glossary.getGlossaryItemLink("ABAC_Simple_Guide")} - Getting started with ABAC rules
|- ${Glossary.getGlossaryItemLink("ABAC_Parameters_Summary")} - Complete list of all 18 parameters
|- ${Glossary.getGlossaryItemLink("ABAC_Object_Properties_Reference")} - Detailed property reference
|- ${Glossary.getGlossaryItemLink("ABAC_Testing_Examples")} - Testing examples and patterns
|
|You can provide optional IDs in the request body to test the rule with specific context.
|
@ -4495,15 +4823,15 @@ trait APIMethods600 {
|
|""".stripMargin,
ExecuteAbacRuleJsonV600(
authenticated_user_id = None,
on_behalf_of_user_id = None,
user_id = None,
authenticated_user_id = Some("c7b6cb47-cb96-4441-8801-35b57456753a"),
on_behalf_of_user_id = Some("a3b5c123-1234-5678-9012-fedcba987654"),
user_id = Some("c7b6cb47-cb96-4441-8801-35b57456753a"),
bank_id = Some("gh.29.uk"),
account_id = Some("8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"),
view_id = None,
transaction_id = None,
transaction_request_id = None,
customer_id = None
view_id = Some("owner"),
transaction_request_id = Some("123456"),
transaction_id = Some("abc123"),
customer_id = Some("customer-id-123")
),
AbacRuleResultJsonV600(
result = true
@ -4569,6 +4897,523 @@ trait APIMethods600 {
}
}
// ============================================================================================================
// USER ATTRIBUTES v6.0.0 - Consistent with other entity attributes
// ============================================================================================================
// "user attributes" = IsPersonal=false (requires roles) - consistent with other entity attributes
// "personal user attributes" = IsPersonal=true (no roles, user manages their own)
// ============================================================================================================
staticResourceDocs += ResourceDoc(
createUserAttribute,
implementedInApiVersion,
nameOf(createUserAttribute),
"POST",
"/users/USER_ID/attributes",
"Create User Attribute",
s"""Create a User Attribute for the user specified by USER_ID.
|
|User Attributes are non-personal attributes (IsPersonal=false) that can be used in ABAC rules.
|They require a role to set, similar to Customer Attributes, Account Attributes, etc.
|
|For personal attributes that users manage themselves, see the /my/personal-user-attributes endpoints.
|
|The type field must be one of "STRING", "INTEGER", "DOUBLE" or "DATE_WITH_DAY"
|
|${userAuthenticationMessage(true)}
|""".stripMargin,
code.api.v5_1_0.UserAttributeJsonV510(
name = "account_type",
`type` = "STRING",
value = "premium"
),
userAttributeResponseJsonV510,
List(
$UserNotLoggedIn,
UserHasMissingRoles,
UserNotFoundByUserId,
InvalidJsonFormat,
UnknownError
),
List(apiTagUser, apiTagUserAttribute, apiTagAttribute),
Some(List(canCreateUserAttribute))
)
lazy val createUserAttribute: OBPEndpoint = {
case "users" :: userId :: "attributes" :: Nil JsonPost json -> _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canCreateUserAttribute, callContext)
(user, callContext) <- NewStyle.function.getUserByUserId(userId, callContext)
failMsg = s"$InvalidJsonFormat The Json body should be the UserAttributeJsonV510"
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[code.api.v5_1_0.UserAttributeJsonV510]
}
failMsg = s"$InvalidJsonFormat The `type` field can only accept: ${UserAttributeType.DOUBLE}, ${UserAttributeType.STRING}, ${UserAttributeType.INTEGER}, ${UserAttributeType.DATE_WITH_DAY}"
userAttributeType <- NewStyle.function.tryons(failMsg, 400, callContext) {
UserAttributeType.withName(postedData.`type`)
}
(userAttribute, callContext) <- NewStyle.function.createOrUpdateUserAttribute(
user.userId,
None,
postedData.name,
userAttributeType,
postedData.value,
false, // IsPersonal = false for user attributes
callContext
)
} yield {
(JSONFactory510.createUserAttributeJson(userAttribute), HttpCode.`201`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getUserAttributes,
implementedInApiVersion,
nameOf(getUserAttributes),
"GET",
"/users/USER_ID/attributes",
"Get User Attributes",
s"""Get User Attributes for the user specified by USER_ID.
|
|Returns non-personal user attributes (IsPersonal=false) that can be used in ABAC rules.
|
|${userAuthenticationMessage(true)}
|""".stripMargin,
EmptyBody,
code.api.v5_1_0.UserAttributesResponseJsonV510(
user_attributes = List(userAttributeResponseJsonV510)
),
List(
$UserNotLoggedIn,
UserHasMissingRoles,
UserNotFoundByUserId,
UnknownError
),
List(apiTagUser, apiTagUserAttribute, apiTagAttribute),
Some(List(canGetUserAttributes))
)
lazy val getUserAttributes: OBPEndpoint = {
case "users" :: userId :: "attributes" :: Nil JsonGet _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canGetUserAttributes, callContext)
(user, callContext) <- NewStyle.function.getUserByUserId(userId, callContext)
(attributes, callContext) <- NewStyle.function.getNonPersonalUserAttributes(user.userId, callContext)
} yield {
(code.api.v5_1_0.UserAttributesResponseJsonV510(attributes.map(JSONFactory510.createUserAttributeJson)), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getUserAttributeById,
implementedInApiVersion,
nameOf(getUserAttributeById),
"GET",
"/users/USER_ID/attributes/USER_ATTRIBUTE_ID",
"Get User Attribute By Id",
s"""Get a User Attribute by USER_ATTRIBUTE_ID for the user specified by USER_ID.
|
|${userAuthenticationMessage(true)}
|""".stripMargin,
EmptyBody,
userAttributeResponseJsonV510,
List(
$UserNotLoggedIn,
UserHasMissingRoles,
UserNotFoundByUserId,
UserAttributeNotFound,
UnknownError
),
List(apiTagUser, apiTagUserAttribute, apiTagAttribute),
Some(List(canGetUserAttributes))
)
lazy val getUserAttributeById: OBPEndpoint = {
case "users" :: userId :: "attributes" :: userAttributeId :: Nil JsonGet _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canGetUserAttributes, callContext)
(user, callContext) <- NewStyle.function.getUserByUserId(userId, callContext)
(attributes, callContext) <- NewStyle.function.getNonPersonalUserAttributes(user.userId, callContext)
attribute <- Future {
attributes.find(_.userAttributeId == userAttributeId)
} map {
unboxFullOrFail(_, callContext, UserAttributeNotFound, 404)
}
} yield {
(JSONFactory510.createUserAttributeJson(attribute), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
updateUserAttribute,
implementedInApiVersion,
nameOf(updateUserAttribute),
"PUT",
"/users/USER_ID/attributes/USER_ATTRIBUTE_ID",
"Update User Attribute",
s"""Update a User Attribute by USER_ATTRIBUTE_ID for the user specified by USER_ID.
|
|${userAuthenticationMessage(true)}
|""".stripMargin,
code.api.v5_1_0.UserAttributeJsonV510(
name = "account_type",
`type` = "STRING",
value = "enterprise"
),
userAttributeResponseJsonV510,
List(
$UserNotLoggedIn,
UserHasMissingRoles,
UserNotFoundByUserId,
UserAttributeNotFound,
InvalidJsonFormat,
UnknownError
),
List(apiTagUser, apiTagUserAttribute, apiTagAttribute),
Some(List(canUpdateUserAttribute))
)
lazy val updateUserAttribute: OBPEndpoint = {
case "users" :: userId :: "attributes" :: userAttributeId :: Nil JsonPut json -> _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canUpdateUserAttribute, callContext)
(user, callContext) <- NewStyle.function.getUserByUserId(userId, callContext)
failMsg = s"$InvalidJsonFormat The Json body should be the UserAttributeJsonV510"
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[code.api.v5_1_0.UserAttributeJsonV510]
}
failMsg = s"$InvalidJsonFormat The `type` field can only accept: ${UserAttributeType.DOUBLE}, ${UserAttributeType.STRING}, ${UserAttributeType.INTEGER}, ${UserAttributeType.DATE_WITH_DAY}"
userAttributeType <- NewStyle.function.tryons(failMsg, 400, callContext) {
UserAttributeType.withName(postedData.`type`)
}
(attributes, callContext) <- NewStyle.function.getNonPersonalUserAttributes(user.userId, callContext)
_ <- Future {
attributes.find(_.userAttributeId == userAttributeId)
} map {
unboxFullOrFail(_, callContext, UserAttributeNotFound, 404)
}
(userAttribute, callContext) <- NewStyle.function.createOrUpdateUserAttribute(
user.userId,
Some(userAttributeId),
postedData.name,
userAttributeType,
postedData.value,
false, // IsPersonal = false for user attributes
callContext
)
} yield {
(JSONFactory510.createUserAttributeJson(userAttribute), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
deleteUserAttribute,
implementedInApiVersion,
nameOf(deleteUserAttribute),
"DELETE",
"/users/USER_ID/attributes/USER_ATTRIBUTE_ID",
"Delete User Attribute",
s"""Delete a User Attribute by USER_ATTRIBUTE_ID for the user specified by USER_ID.
|
|${userAuthenticationMessage(true)}
|""".stripMargin,
EmptyBody,
EmptyBody,
List(
$UserNotLoggedIn,
UserHasMissingRoles,
UserNotFoundByUserId,
UserAttributeNotFound,
UnknownError
),
List(apiTagUser, apiTagUserAttribute, apiTagAttribute),
Some(List(canDeleteUserAttribute))
)
lazy val deleteUserAttribute: OBPEndpoint = {
case "users" :: userId :: "attributes" :: userAttributeId :: Nil JsonDelete _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
_ <- NewStyle.function.hasEntitlement("", u.userId, canDeleteUserAttribute, callContext)
(user, callContext) <- NewStyle.function.getUserByUserId(userId, callContext)
(attributes, callContext) <- NewStyle.function.getNonPersonalUserAttributes(user.userId, callContext)
_ <- Future {
attributes.find(_.userAttributeId == userAttributeId)
} map {
unboxFullOrFail(_, callContext, UserAttributeNotFound, 404)
}
(deleted, callContext) <- Connector.connector.vend.deleteUserAttribute(
userAttributeId,
callContext
) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
} yield {
(Full(deleted), HttpCode.`204`(callContext))
}
}
}
// ============================================================================================================
// PERSONAL DATA - User manages their own personal data
// ============================================================================================================
staticResourceDocs += ResourceDoc(
createMyPersonalUserAttribute,
implementedInApiVersion,
nameOf(createMyPersonalUserAttribute),
"POST",
"/my/personal-data",
"Create My Personal Data",
s"""Create Personal Data for the currently authenticated user.
|
|Personal Data (IsPersonal=true) is managed by the user themselves and does not require special roles.
|This data is not available in ABAC rules for privacy reasons.
|
|For non-personal attributes that can be used in ABAC rules, see the /users/USER_ID/attributes endpoints.
|
|The type field must be one of "STRING", "INTEGER", "DOUBLE" or "DATE_WITH_DAY"
|
|${userAuthenticationMessage(true)}
|""".stripMargin,
code.api.v5_1_0.UserAttributeJsonV510(
name = "favorite_color",
`type` = "STRING",
value = "blue"
),
userAttributeResponseJsonV510,
List(
$UserNotLoggedIn,
InvalidJsonFormat,
UnknownError
),
List(apiTagUser, apiTagUserAttribute, apiTagAttribute),
Some(List())
)
lazy val createMyPersonalUserAttribute: OBPEndpoint = {
case "my" :: "personal-data" :: Nil JsonPost json -> _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
failMsg = s"$InvalidJsonFormat The Json body should be the UserAttributeJsonV510"
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[code.api.v5_1_0.UserAttributeJsonV510]
}
failMsg = s"$InvalidJsonFormat The `type` field can only accept: ${UserAttributeType.DOUBLE}, ${UserAttributeType.STRING}, ${UserAttributeType.INTEGER}, ${UserAttributeType.DATE_WITH_DAY}"
userAttributeType <- NewStyle.function.tryons(failMsg, 400, callContext) {
UserAttributeType.withName(postedData.`type`)
}
(userAttribute, callContext) <- NewStyle.function.createOrUpdateUserAttribute(
u.userId,
None,
postedData.name,
userAttributeType,
postedData.value,
true, // IsPersonal = true for personal user attributes
callContext
)
} yield {
(JSONFactory510.createUserAttributeJson(userAttribute), HttpCode.`201`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getMyPersonalUserAttributes,
implementedInApiVersion,
nameOf(getMyPersonalUserAttributes),
"GET",
"/my/personal-data",
"Get My Personal Data",
s"""Get Personal Data for the currently authenticated user.
|
|Returns personal data (IsPersonal=true) that is managed by the user.
|
|${userAuthenticationMessage(true)}
|""".stripMargin,
EmptyBody,
code.api.v5_1_0.UserAttributesResponseJsonV510(
user_attributes = List(userAttributeResponseJsonV510)
),
List(
$UserNotLoggedIn,
UnknownError
),
List(apiTagUser, apiTagUserAttribute, apiTagAttribute),
Some(List())
)
lazy val getMyPersonalUserAttributes: OBPEndpoint = {
case "my" :: "personal-data" :: Nil JsonGet _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
(attributes, callContext) <- NewStyle.function.getPersonalUserAttributes(u.userId, callContext)
} yield {
(code.api.v5_1_0.UserAttributesResponseJsonV510(attributes.map(JSONFactory510.createUserAttributeJson)), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
getMyPersonalUserAttributeById,
implementedInApiVersion,
nameOf(getMyPersonalUserAttributeById),
"GET",
"/my/personal-data/USER_ATTRIBUTE_ID",
"Get My Personal Data By Id",
s"""Get Personal Data by USER_ATTRIBUTE_ID for the currently authenticated user.
|
|${userAuthenticationMessage(true)}
|""".stripMargin,
EmptyBody,
userAttributeResponseJsonV510,
List(
$UserNotLoggedIn,
UserAttributeNotFound,
UnknownError
),
List(apiTagUser, apiTagUserAttribute, apiTagAttribute),
Some(List())
)
lazy val getMyPersonalUserAttributeById: OBPEndpoint = {
case "my" :: "personal-data" :: userAttributeId :: Nil JsonGet _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
(attributes, callContext) <- NewStyle.function.getPersonalUserAttributes(u.userId, callContext)
attribute <- Future {
attributes.find(_.userAttributeId == userAttributeId)
} map {
unboxFullOrFail(_, callContext, UserAttributeNotFound, 404)
}
} yield {
(JSONFactory510.createUserAttributeJson(attribute), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
updateMyPersonalUserAttribute,
implementedInApiVersion,
nameOf(updateMyPersonalUserAttribute),
"PUT",
"/my/personal-data/USER_ATTRIBUTE_ID",
"Update My Personal Data",
s"""Update Personal Data by USER_ATTRIBUTE_ID for the currently authenticated user.
|
|${userAuthenticationMessage(true)}
|""".stripMargin,
code.api.v5_1_0.UserAttributeJsonV510(
name = "favorite_color",
`type` = "STRING",
value = "green"
),
userAttributeResponseJsonV510,
List(
$UserNotLoggedIn,
UserAttributeNotFound,
InvalidJsonFormat,
UnknownError
),
List(apiTagUser, apiTagUserAttribute, apiTagAttribute),
Some(List())
)
lazy val updateMyPersonalUserAttribute: OBPEndpoint = {
case "my" :: "personal-data" :: userAttributeId :: Nil JsonPut json -> _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
failMsg = s"$InvalidJsonFormat The Json body should be the UserAttributeJsonV510"
postedData <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[code.api.v5_1_0.UserAttributeJsonV510]
}
failMsg = s"$InvalidJsonFormat The `type` field can only accept: ${UserAttributeType.DOUBLE}, ${UserAttributeType.STRING}, ${UserAttributeType.INTEGER}, ${UserAttributeType.DATE_WITH_DAY}"
userAttributeType <- NewStyle.function.tryons(failMsg, 400, callContext) {
UserAttributeType.withName(postedData.`type`)
}
(attributes, callContext) <- NewStyle.function.getPersonalUserAttributes(u.userId, callContext)
_ <- Future {
attributes.find(_.userAttributeId == userAttributeId)
} map {
unboxFullOrFail(_, callContext, UserAttributeNotFound, 404)
}
(userAttribute, callContext) <- NewStyle.function.createOrUpdateUserAttribute(
u.userId,
Some(userAttributeId),
postedData.name,
userAttributeType,
postedData.value,
true, // IsPersonal = true for personal user attributes
callContext
)
} yield {
(JSONFactory510.createUserAttributeJson(userAttribute), HttpCode.`200`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
deleteMyPersonalUserAttribute,
implementedInApiVersion,
nameOf(deleteMyPersonalUserAttribute),
"DELETE",
"/my/personal-data/USER_ATTRIBUTE_ID",
"Delete My Personal Data",
s"""Delete Personal Data by USER_ATTRIBUTE_ID for the currently authenticated user.
|
|${userAuthenticationMessage(true)}
|""".stripMargin,
EmptyBody,
EmptyBody,
List(
$UserNotLoggedIn,
UserAttributeNotFound,
UnknownError
),
List(apiTagUser, apiTagUserAttribute, apiTagAttribute),
Some(List())
)
lazy val deleteMyPersonalUserAttribute: OBPEndpoint = {
case "my" :: "personal-data" :: userAttributeId :: Nil JsonDelete _ => {
cc => implicit val ec = EndpointContext(Some(cc))
for {
(Full(u), callContext) <- authenticatedAccess(cc)
(attributes, callContext) <- NewStyle.function.getPersonalUserAttributes(u.userId, callContext)
_ <- Future {
attributes.find(_.userAttributeId == userAttributeId)
} map {
unboxFullOrFail(_, callContext, UserAttributeNotFound, 404)
}
(deleted, callContext) <- Connector.connector.vend.deleteUserAttribute(
userAttributeId,
callContext
) map {
i => (connectorEmptyResponse(i._1, callContext), i._2)
}
} yield {
(Full(deleted), HttpCode.`204`(callContext))
}
}
}
}
}

View File

@ -324,8 +324,8 @@ case class ExecuteAbacRuleJsonV600(
bank_id: Option[String],
account_id: Option[String],
view_id: Option[String],
transaction_id: Option[String],
transaction_request_id: Option[String],
transaction_id: Option[String],
customer_id: Option[String]
)
@ -333,6 +333,54 @@ case class AbacRuleResultJsonV600(
result: Boolean
)
case class ValidateAbacRuleJsonV600(
rule_code: String
)
case class ValidateAbacRuleSuccessJsonV600(
valid: Boolean,
message: String
)
case class ValidateAbacRuleErrorDetailsJsonV600(
error_type: String
)
case class ValidateAbacRuleFailureJsonV600(
valid: Boolean,
error: String,
message: String,
details: ValidateAbacRuleErrorDetailsJsonV600
)
case class AbacParameterJsonV600(
name: String,
`type`: String,
description: String,
required: Boolean,
category: String
)
case class AbacObjectPropertyJsonV600(
name: String,
`type`: String,
description: String
)
case class AbacObjectTypeJsonV600(
name: String,
description: String,
properties: List[AbacObjectPropertyJsonV600]
)
case class AbacRuleSchemaJsonV600(
parameters: List[AbacParameterJsonV600],
object_types: List[AbacObjectTypeJsonV600],
examples: List[String],
available_operators: List[String],
notes: List[String]
)
object JSONFactory600 extends CustomJsonFormats with MdcLoggable{
def createCurrentUsageJson(rateLimits: List[((Option[Long], Option[Long]), LimitCallPeriod)]): Option[RedisCallLimitJson] = {