mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:47:18 +00:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
0db2af0bd2
30
flushall_build_and_run.sh
Executable file
30
flushall_build_and_run.sh
Executable 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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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 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")
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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 -> _=> {
|
||||
|
||||
@ -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))
|
||||
)
|
||||
|
||||
|
||||
@ -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))
|
||||
)
|
||||
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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] = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user