OBP-API/ideas/obp-abac-structured-examples-implementation-plan.md
simonredfern efc1868fd4 BREAKING CHANGE: Switch active-rate-limits endpoint to hour-based format
Changed from full timestamp to hour-only format to match implementation.

OLD: /active-rate-limits/2025-12-31T13:34:46Z (YYYY-MM-DDTHH:MM:SSZ)
NEW: /active-rate-limits/2025-12-31-13 (YYYY-MM-DD-HH)

Benefits:
- API now matches actual implementation (hour-level caching)
- Eliminates timezone/minute truncation confusion
- Clearer semantics: 'active during this hour' not 'at this second'
- Direct cache key mapping improves performance
- Simpler date parsing (no timezone handling needed)

Files changed:
- APIMethods600.scala: Updated endpoint and date parsing
- RateLimitsTest.scala: Updated all test cases to new format
- Glossary.scala: Updated API documentation
- introductory_system_documentation.md: Updated user docs

This is a breaking change but necessary to align API with implementation.
Rate limits are cached and queried at hour granularity, so the API
should reflect that reality.
2025-12-30 17:35:38 +01:00

424 lines
15 KiB
Markdown

# OBP ABAC Structured Examples Implementation Plan
## Goal
Convert the ABAC rule schema examples from simple strings to structured objects with:
- `category`: String - Grouping/category of the example
- `title`: String - Short descriptive title
- `code`: String - The actual Scala code example
- `description`: String - Detailed explanation of what the code does
## Example Structure
```json
{
"category": "User Attributes",
"title": "Account Type Check",
"code": "userAttributes.exists(attr => attr.name == \"account_type\" && attr.value == \"premium\")",
"description": "Check if target user has premium account type attribute"
}
```
## Implementation Steps
### Step 1: Update JSON Case Class
**File**: `OBP-API/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala`
**Current code** (around line 413-419):
```scala
case class AbacRuleSchemaJsonV600(
parameters: List[AbacParameterJsonV600],
object_types: List[AbacObjectTypeJsonV600],
examples: List[String],
available_operators: List[String],
notes: List[String]
)
```
**Change to**:
```scala
case class AbacRuleExampleJsonV600(
category: String,
title: String,
code: String,
description: String
)
case class AbacRuleSchemaJsonV600(
parameters: List[AbacParameterJsonV600],
object_types: List[AbacObjectTypeJsonV600],
examples: List[AbacRuleExampleJsonV600], // Changed from List[String]
available_operators: List[String],
notes: List[String]
)
```
### Step 2: Update API Endpoint
**File**: `OBP-API/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala`
**Location**: The `getAbacRuleSchema` endpoint (around line 4891-5070)
**Find this line** (around line 5021):
```scala
examples = List(
```
**Replace the entire examples List with structured examples**.
See the comprehensive list in Section 3 below.
### Step 3: Structured Examples List
Replace the `examples = List(...)` with this:
```scala
examples = List(
// === Authenticated User Examples ===
AbacRuleExampleJsonV600(
category = "Authenticated User",
title = "Check Email Domain",
code = """authenticatedUser.emailAddress.contains("@example.com")""",
description = "Verify authenticated user's email belongs to a specific domain"
),
AbacRuleExampleJsonV600(
category = "Authenticated User",
title = "Check Provider",
code = """authenticatedUser.provider == "obp"""",
description = "Verify the authentication provider is OBP"
),
AbacRuleExampleJsonV600(
category = "Authenticated User",
title = "User Not Deleted",
code = """!authenticatedUser.isDeleted.getOrElse(false)""",
description = "Ensure the authenticated user account is not marked as deleted"
),
// === Authenticated User Attributes ===
AbacRuleExampleJsonV600(
category = "Authenticated User Attributes",
title = "Admin Role Check",
code = """authenticatedUserAttributes.exists(attr => attr.name == "role" && attr.value == "admin")""",
description = "Check if authenticated user has admin role attribute"
),
AbacRuleExampleJsonV600(
category = "Authenticated User Attributes",
title = "Department Check",
code = """authenticatedUserAttributes.find(_.name == "department").exists(_.value == "finance")""",
description = "Check if user belongs to finance department"
),
AbacRuleExampleJsonV600(
category = "Authenticated User Attributes",
title = "Multiple Role Check",
code = """authenticatedUserAttributes.exists(attr => attr.name == "role" && List("admin", "manager", "supervisor").contains(attr.value))""",
description = "Check if user has any of the specified management roles"
),
// === Target User Examples ===
AbacRuleExampleJsonV600(
category = "Target User",
title = "Self Access",
code = """userOpt.exists(_.userId == authenticatedUser.userId)""",
description = "Check if target user is the authenticated user (self-access)"
),
AbacRuleExampleJsonV600(
category = "Target User",
title = "Same Email Domain",
code = """userOpt.exists(u => authenticatedUser.emailAddress.split("@")(1) == u.emailAddress.split("@")(1))""",
description = "Check both users share the same email domain"
),
// === User Attributes ===
AbacRuleExampleJsonV600(
category = "User Attributes",
title = "Premium Account Type",
code = """userAttributes.exists(attr => attr.name == "account_type" && attr.value == "premium")""",
description = "Check if target user has premium account type attribute"
),
AbacRuleExampleJsonV600(
category = "User Attributes",
title = "KYC Verified",
code = """userAttributes.exists(attr => attr.name == "kyc_status" && attr.value == "verified")""",
description = "Verify target user has completed KYC verification"
),
// === Account Examples ===
AbacRuleExampleJsonV600(
category = "Account",
title = "Balance Threshold",
code = """accountOpt.exists(_.balance > 1000)""",
description = "Check if account balance exceeds threshold"
),
AbacRuleExampleJsonV600(
category = "Account",
title = "Currency and Balance",
code = """accountOpt.exists(acc => acc.currency == "USD" && acc.balance > 5000)""",
description = "Check account has USD currency and balance over 5000"
),
AbacRuleExampleJsonV600(
category = "Account",
title = "Savings Account Type",
code = """accountOpt.exists(_.accountType == "SAVINGS")""",
description = "Verify account is a savings account"
),
// === Transaction Examples ===
AbacRuleExampleJsonV600(
category = "Transaction",
title = "Amount Limit",
code = """transactionOpt.exists(_.amount < 10000)""",
description = "Check transaction amount is below limit"
),
AbacRuleExampleJsonV600(
category = "Transaction",
title = "Transfer Type",
code = """transactionOpt.exists(_.transactionType.contains("TRANSFER"))""",
description = "Verify transaction is a transfer type"
),
// === Customer Examples ===
AbacRuleExampleJsonV600(
category = "Customer",
title = "Email Matches User",
code = """customerOpt.exists(_.email == authenticatedUser.emailAddress)""",
description = "Verify customer email matches authenticated user"
),
AbacRuleExampleJsonV600(
category = "Customer",
title = "Active Relationship",
code = """customerOpt.exists(_.relationshipStatus == "ACTIVE")""",
description = "Check customer has active relationship status"
),
// === Object-to-Object Comparisons ===
AbacRuleExampleJsonV600(
category = "Object Comparisons - User",
title = "Self Access by User ID",
code = """userOpt.exists(_.userId == authenticatedUser.userId)""",
description = "Verify target user ID matches authenticated user (self-access)"
),
AbacRuleExampleJsonV600(
category = "Object Comparisons - Customer/User",
title = "Customer Email Matches Target User",
code = """customerOpt.exists(c => userOpt.exists(u => c.email == u.emailAddress))""",
description = "Verify customer email matches target user"
),
AbacRuleExampleJsonV600(
category = "Object Comparisons - Account/Transaction",
title = "Transaction Within Balance",
code = """transactionOpt.exists(t => accountOpt.exists(a => t.amount < a.balance))""",
description = "Verify transaction amount is less than account balance"
),
AbacRuleExampleJsonV600(
category = "Object Comparisons - Account/Transaction",
title = "Currency Match",
code = """transactionOpt.exists(t => accountOpt.exists(a => t.currency == a.currency))""",
description = "Verify transaction currency matches account currency"
),
AbacRuleExampleJsonV600(
category = "Object Comparisons - Account/Transaction",
title = "No Overdraft",
code = """transactionOpt.exists(t => accountOpt.exists(a => a.balance - t.amount >= 0))""",
description = "Ensure transaction won't overdraw account"
),
// === Attribute Cross-Comparisons ===
AbacRuleExampleJsonV600(
category = "Object Comparisons - Attributes",
title = "User Tier Matches Account Tier",
code = """userAttributes.exists(ua => ua.name == "tier" && accountAttributes.exists(aa => aa.name == "tier" && ua.value == aa.value))""",
description = "Verify user tier level matches account tier level"
),
AbacRuleExampleJsonV600(
category = "Object Comparisons - Attributes",
title = "Department Match",
code = """authenticatedUserAttributes.exists(ua => ua.name == "department" && accountAttributes.exists(aa => aa.name == "department" && ua.value == aa.value))""",
description = "Verify user department matches account department"
),
// === Complex Multi-Object Examples ===
AbacRuleExampleJsonV600(
category = "Complex Scenarios",
title = "Trusted Employee Access",
code = """authenticatedUser.emailAddress.endsWith("@bank.com") && accountOpt.exists(_.balance > 0) && bankOpt.exists(_.bankId.value == "gh.29.uk")""",
description = "Allow bank employees to access accounts with positive balance at specific bank"
),
AbacRuleExampleJsonV600(
category = "Complex Scenarios",
title = "Manager Accessing Team Data",
code = """authenticatedUserAttributes.exists(_.name == "role" && _.value == "manager") && userOpt.exists(_.userId != authenticatedUser.userId)""",
description = "Allow managers to access other users' data"
),
AbacRuleExampleJsonV600(
category = "Complex Scenarios",
title = "VIP with Premium Account",
code = """customerAttributes.exists(_.name == "vip_status" && _.value == "true") && accountAttributes.exists(_.name == "account_tier" && _.value == "premium")""",
description = "Check for VIP customer with premium account combination"
),
// === Chained Validation ===
AbacRuleExampleJsonV600(
category = "Chained Validation",
title = "Full Customer Chain",
code = """userOpt.exists(u => customerOpt.exists(c => c.email == u.emailAddress && accountOpt.exists(a => transactionOpt.exists(t => t.accountId.value == a.accountId.value))))""",
description = "Validate complete chain: User → Customer → Account → Transaction"
),
// === Real-World Business Logic ===
AbacRuleExampleJsonV600(
category = "Business Logic",
title = "Loan Approval",
code = """customerAttributes.exists(ca => ca.name == "credit_score" && ca.value.toInt > 650) && accountOpt.exists(_.balance > 5000)""",
description = "Check credit score above 650 and minimum balance for loan approval"
),
AbacRuleExampleJsonV600(
category = "Business Logic",
title = "Wire Transfer Authorization",
code = """transactionOpt.exists(t => t.amount < 100000 && t.transactionType.exists(_.contains("WIRE"))) && authenticatedUserAttributes.exists(_.name == "wire_authorized")""",
description = "Verify user is authorized for wire transfers under limit"
),
AbacRuleExampleJsonV600(
category = "Business Logic",
title = "Joint Account Access",
code = """accountOpt.exists(a => a.accountHolders.exists(h => h.userId == authenticatedUser.userId || h.emailAddress == authenticatedUser.emailAddress))""",
description = "Allow access if user is one of the joint account holders"
),
// === Safe Option Handling ===
AbacRuleExampleJsonV600(
category = "Safe Patterns",
title = "Pattern Matching",
code = """userOpt match { case Some(u) => u.userId == authenticatedUser.userId case None => false }""",
description = "Safe Option handling using pattern matching"
),
AbacRuleExampleJsonV600(
category = "Safe Patterns",
title = "Using exists()",
code = """accountOpt.exists(_.balance > 0)""",
description = "Safe way to check Option value using exists method"
),
AbacRuleExampleJsonV600(
category = "Safe Patterns",
title = "Using forall()",
code = """userOpt.forall(!_.isDeleted.getOrElse(false))""",
description = "Safe negative condition using forall (returns true if None)"
),
// === Error Prevention ===
AbacRuleExampleJsonV600(
category = "Common Mistakes",
title = "WRONG - Unsafe get()",
code = """accountOpt.get.balance > 1000""",
description = "❌ WRONG: Using .get without checking isDefined (can throw exception)"
),
AbacRuleExampleJsonV600(
category = "Common Mistakes",
title = "CORRECT - Safe exists()",
code = """accountOpt.exists(_.balance > 1000)""",
description = "✅ CORRECT: Safe way to check account balance using exists()"
)
),
```
## Benefits of Structured Examples
### 1. Better UI/UX
- Examples can be grouped by category in the UI
- Searchable by title or description
- Code can be syntax highlighted separately
- Easier to filter and navigate
### 2. Better for AI/LLM Integration
- Clear structure for AI to understand
- Category helps with semantic search
- Description provides context for code generation
- Title provides quick summary
### 3. Better for Documentation
- Can generate categorized documentation automatically
- Can create searchable example libraries
- Easier to maintain and update
- Better for auto-completion in IDEs
### 4. API Response Example
**Before (flat strings)**:
```json
{
"examples": [
"// Check if authenticated user matches target user",
"authenticatedUser.userId == userOpt.get.userId"
]
}
```
**After (structured)**:
```json
{
"examples": [
{
"category": "Target User",
"title": "Self Access",
"code": "userOpt.exists(_.userId == authenticatedUser.userId)",
"description": "Check if target user is the authenticated user (self-access)"
}
]
}
```
## Testing
After implementation, test:
1. **API Response**: Call `GET /obp/v6.0.0/management/abac-rules-schema` and verify JSON structure
2. **Compilation**: Ensure Scala code compiles without errors
3. **Frontend**: Update any frontend code that consumes this endpoint
4. **Backward Compatibility**: Consider if any clients depend on the old string format
## Rollout Strategy
### Option A: Breaking Change (Recommended)
- Implement in v6.0.0 as shown above
- Document as breaking change in release notes
- Provide migration guide for clients
### Option B: Maintain Backward Compatibility
- Add new field `structured_examples` alongside existing `examples`
- Keep old `examples` as List[String] with just the code
- Deprecate old field, remove in v7.0.0
## Full Example Count
The implementation should include approximately **60-80 structured examples** covering:
- 3-4 examples per parameter (19 parameters) = ~60 examples
- 10-15 object-to-object comparison examples
- 5-10 complex multi-object scenarios
- 5 real-world business logic examples
- 4-5 safe pattern examples
- 2-3 error prevention examples
Total: ~80-100 examples
## Notes
- Use triple quotes `"""` for code strings to avoid escaping issues
- Keep code examples concise but realistic
- Ensure all examples are valid Scala syntax
- Test examples can actually compile/execute
- Categories should be consistent and logical
- Descriptions should explain the "why" not just the "what"
## Related Files
- Enhancement spec: `obp-abac-schema-examples-enhancement.md`
- Implementation summary (after): `obp-abac-schema-examples-implementation-summary.md`
---
**Status**: Ready for Implementation
**Priority**: Medium
**Estimated Effort**: 2-3 hours
**Version**: OBP API v6.0.0