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

This commit is contained in:
hongwei 2025-12-12 11:04:02 +01:00
commit 5a418e556c
7 changed files with 1203 additions and 28 deletions

View File

@ -0,0 +1,254 @@
# WebUI Props - Alphabetical List
Complete list of all `webui_*` properties used in OBP-API, sorted alphabetically.
These properties can be:
- Set in props files (e.g., `default.props`)
- Stored in the database via WebUiProps table
- Retrieved via API: `GET /obp/v6.0.0/webui-props/{PROP_NAME}`
---
## Complete List (56 properties)
1. `webui_agree_terms_url`
2. `webui_api_documentation_bottom_url`
3. `webui_api_documentation_url`
4. `webui_api_explorer_url`
5. `webui_api_manager_url`
6. `webui_customer_user_invitation_email_from`
7. `webui_customer_user_invitation_email_html_text`
8. `webui_customer_user_invitation_email_subject`
9. `webui_customer_user_invitation_email_text`
10. `webui_developer_user_invitation_email_from`
11. `webui_developer_user_invitation_email_html_text`
12. `webui_developer_user_invitation_email_subject`
13. `webui_developer_user_invitation_email_text`
14. `webui_direct_login_documentation_url`
15. `webui_dummy_user_logins`
16. `webui_faq_data_text`
17. `webui_faq_email`
18. `webui_faq_url`
19. `webui_favicon_link_url`
20. `webui_featured_sdks_external_link`
21. `webui_footer2_logo_left_url`
22. `webui_footer2_middle_text`
23. `webui_get_started_text`
24. `webui_header_logo_left_url`
25. `webui_header_logo_right_url`
26. `webui_index_page_about_section_background_image_url`
27. `webui_index_page_about_section_text`
28. `webui_legal_notice_html_text`
29. `webui_login_button_text`
30. `webui_login_page_instruction_title`
31. `webui_login_page_special_instructions`
32. `webui_main_faq_external_link`
33. `webui_main_partners`
34. `webui_main_style_sheet`
35. `webui_oauth_1_documentation_url`
36. `webui_oauth_2_documentation_url`
37. `webui_obp_cli_url`
38. `webui_override_style_sheet`
39. `webui_page_title_prefix`
40. `webui_post_consumer_registration_more_info_text`
41. `webui_post_consumer_registration_more_info_url`
42. `webui_post_consumer_registration_submit_button_value`
43. `webui_post_user_invitation_submit_button_value`
44. `webui_post_user_invitation_terms_and_conditions_checkbox_value`
45. `webui_privacy_policy`
46. `webui_privacy_policy_url`
47. `webui_sandbox_introduction`
48. `webui_sdks_url`
49. `webui_show_dummy_user_tokens`
50. `webui_signup_body_password_repeat_text`
51. `webui_signup_form_submit_button_value`
52. `webui_signup_form_title_text`
53. `webui_social_handle`
54. `webui_social_logo_url`
55. `webui_social_title`
56. `webui_social_url`
57. `webui_subscriptions_button_text`
58. `webui_subscriptions_invitation_text`
59. `webui_subscriptions_url`
60. `webui_support_email`
61. `webui_support_platform_url`
62. `webui_terms_and_conditions`
63. `webui_top_text`
64. `webui_user_invitation_notice_text`
65. `webui_vendor_support_html_url`
---
## Properties by Category
### Branding & UI
- `webui_favicon_link_url`
- `webui_footer2_logo_left_url`
- `webui_footer2_middle_text`
- `webui_header_logo_left_url`
- `webui_header_logo_right_url`
- `webui_index_page_about_section_background_image_url`
- `webui_index_page_about_section_text`
- `webui_main_style_sheet`
- `webui_override_style_sheet`
- `webui_page_title_prefix`
- `webui_top_text`
### Documentation & Links
- `webui_agree_terms_url`
- `webui_api_documentation_bottom_url`
- `webui_api_documentation_url`
- `webui_api_explorer_url`
- `webui_api_manager_url`
- `webui_direct_login_documentation_url`
- `webui_faq_url`
- `webui_featured_sdks_external_link`
- `webui_main_faq_external_link`
- `webui_oauth_1_documentation_url`
- `webui_oauth_2_documentation_url`
- `webui_obp_cli_url`
- `webui_privacy_policy_url`
- `webui_sdks_url`
- `webui_support_platform_url`
- `webui_vendor_support_html_url`
### Login & Signup
- `webui_login_button_text`
- `webui_login_page_instruction_title`
- `webui_login_page_special_instructions`
- `webui_signup_body_password_repeat_text`
- `webui_signup_form_submit_button_value`
- `webui_signup_form_title_text`
### Legal & Terms
- `webui_legal_notice_html_text`
- `webui_privacy_policy`
- `webui_terms_and_conditions`
### User Invitations - Customer
- `webui_customer_user_invitation_email_from`
- `webui_customer_user_invitation_email_html_text`
- `webui_customer_user_invitation_email_subject`
- `webui_customer_user_invitation_email_text`
### User Invitations - Developer
- `webui_developer_user_invitation_email_from`
- `webui_developer_user_invitation_email_html_text`
- `webui_developer_user_invitation_email_subject`
- `webui_developer_user_invitation_email_text`
### User Invitations - General
- `webui_post_user_invitation_submit_button_value`
- `webui_post_user_invitation_terms_and_conditions_checkbox_value`
- `webui_user_invitation_notice_text`
### Consumer Registration
- `webui_post_consumer_registration_more_info_text`
- `webui_post_consumer_registration_more_info_url`
- `webui_post_consumer_registration_submit_button_value`
### Developer Tools
- `webui_dummy_user_logins`
- `webui_show_dummy_user_tokens`
### Support & Contact
- `webui_faq_data_text`
- `webui_faq_email`
- `webui_support_email`
### Social Media
- `webui_social_handle`
- `webui_social_logo_url`
- `webui_social_title`
- `webui_social_url`
### Subscriptions
- `webui_subscriptions_button_text`
- `webui_subscriptions_invitation_text`
- `webui_subscriptions_url`
### Other
- `webui_get_started_text`
- `webui_main_partners`
- `webui_sandbox_introduction`
---
## Environment Variable Mapping
WebUI props can be set via environment variables with the `OBP_` prefix:
```bash
# Props file format:
webui_api_explorer_url=https://apiexplorer.example.com
# Environment variable format:
OBP_WEBUI_API_EXPLORER_URL=https://apiexplorer.example.com
```
**Conversion rule:**
- Add `OBP_` prefix
- Convert to UPPERCASE
- Replace `.` with `_`
---
## API Endpoints
### Get Single WebUI Prop
```
GET /obp/v6.0.0/webui-props/{WEBUI_PROP_NAME}
GET /obp/v6.0.0/webui-props/{WEBUI_PROP_NAME}?active=true
```
### Get All WebUI Props
```
GET /obp/v6.0.0/management/webui_props
GET /obp/v6.0.0/management/webui_props?what=active
GET /obp/v6.0.0/management/webui_props?what=database
GET /obp/v6.0.0/management/webui_props?what=config
```
---
## Usage Examples
### In Props File
```properties
webui_api_explorer_url=https://apiexplorer.openbankproject.com
webui_header_logo_left_url=https://static.openbankproject.com/logo.png
webui_override_style_sheet=https://static.openbankproject.com/css/custom.css
```
### As Environment Variables
```yaml
env:
- name: OBP_WEBUI_API_EXPLORER_URL
value: 'https://apiexplorer.openbankproject.com'
- name: OBP_WEBUI_HEADER_LOGO_LEFT_URL
value: 'https://static.openbankproject.com/logo.png'
- name: OBP_WEBUI_OVERRIDE_STYLE_SHEET
value: 'https://static.openbankproject.com/css/custom.css'
```
---
## Notes
- All webui props are **optional** - the system has default values
- Database values take **precedence** over props file values
- Use `?active=true` query parameter to get database value OR fallback to default
- Props are case-sensitive (always use lowercase `webui_`)
- User invitation email props were renamed in Sept 2021 (see release notes)
---
## Related Documentation
- API Glossary: `webui_props` entry
- User Invitation Guide: `USER_INVITATION_API_ENDPOINTS.md`
- WebUI Props Endpoint: `WEBUI_PROP_SINGLE_GET_ENDPOINT.md`
---
**Total Count:** 65 webui properties

View File

@ -0,0 +1,277 @@
# WebUI Props API - Logging Guide
## Overview
The WebUI Props endpoints in v6.0.0 have **extensive logging** to help with debugging and monitoring. This guide shows you what to search for in your logs.
---
## Logged Endpoints
### 1. Get All WebUI Props
**Endpoint:** `GET /obp/v6.0.0/management/webui_props`
### 2. Get Single WebUI Prop
**Endpoint:** `GET /obp/v6.0.0/webui-props/{PROP_NAME}`
---
## Log Patterns for GET /management/webui_props
### Entry Log
```
========== GET /obp/v6.0.0/management/webui_props called with what={VALUE} ==========
```
**Search for:**
```bash
grep "GET /obp/v6.0.0/management/webui_props called" logs/obp-api.log
```
**Example output:**
```
2025-01-15 10:23:45 INFO - ========== GET /obp/v6.0.0/management/webui_props called with what=active ==========
```
---
### Result Summary Log
```
========== GET /obp/v6.0.0/management/webui_props returning {COUNT} records ==========
```
**Search for:**
```bash
grep "GET /obp/v6.0.0/management/webui_props returning" logs/obp-api.log
```
**Example output:**
```
2025-01-15 10:23:45 INFO - ========== GET /obp/v6.0.0/management/webui_props returning 65 records ==========
```
---
### Individual Property Logs
```
- name: {PROP_NAME}, value: {PROP_VALUE}, webUiPropsId: {ID}
```
**Search for:**
```bash
grep "name: webui_" logs/obp-api.log
```
**Example output:**
```
2025-01-15 10:23:45 INFO - - name: webui_api_explorer_url, value: https://apiexplorer.example.com, webUiPropsId: Some(web-ui-props-id)
2025-01-15 10:23:45 INFO - - name: webui_header_logo_left_url, value: https://static.example.com/logo.png, webUiPropsId: Some(default)
```
---
### Exit Log
```
========== END GET /obp/v6.0.0/management/webui_props ==========
```
**Search for:**
```bash
grep "END GET /obp/v6.0.0/management/webui_props" logs/obp-api.log
```
---
## Log Patterns for GET /webui-props/{PROP_NAME}
### No Explicit Entry/Exit Logs
The single property endpoint (`GET /webui-props/{PROP_NAME}`) does **NOT** have dedicated entry/exit logs like the management endpoint.
However, you can still track it through:
### Standard API Request Logs
```bash
grep "GET /obp/v6.0.0/webui-props/" logs/obp-api.log
```
### Error Logs (if property not found)
```
OBP-08003: WebUi prop not found. Please specify a valid value for WEBUI_PROP_NAME.
```
**Search for:**
```bash
grep "OBP-08003" logs/obp-api.log
grep "WebUi prop not found" logs/obp-api.log
```
---
## Complete Log Sequence Example
When calling `GET /obp/v6.0.0/management/webui_props?what=active`:
```
2025-01-15 10:23:45.123 [http-nio-8080-exec-1] INFO code.api.v6_0_0.APIMethods600$ - ========== GET /obp/v6.0.0/management/webui_props called with what=active ==========
2025-01-15 10:23:45.234 [http-nio-8080-exec-1] INFO code.api.v6_0_0.APIMethods600$ - ========== GET /obp/v6.0.0/management/webui_props returning 65 records ==========
2025-01-15 10:23:45.235 [http-nio-8080-exec-1] INFO code.api.v6_0_0.APIMethods600$ - name: webui_agree_terms_url, value: https://example.com/terms, webUiPropsId: Some(default)
2025-01-15 10:23:45.236 [http-nio-8080-exec-1] INFO code.api.v6_0_0.APIMethods600$ - name: webui_api_documentation_url, value: https://docs.example.com, webUiPropsId: Some(default)
2025-01-15 10:23:45.237 [http-nio-8080-exec-1] INFO code.api.v6_0_0.APIMethods600$ - name: webui_api_explorer_url, value: https://apiexplorer.example.com, webUiPropsId: Some(web-ui-123)
...
(63 more property logs)
...
2025-01-15 10:23:45.300 [http-nio-8080-exec-1] INFO code.api.v6_0_0.APIMethods600$ - ========== END GET /obp/v6.0.0/management/webui_props ==========
```
---
## Useful grep Commands
### 1. Find all webui_props calls
```bash
grep "GET /obp/v6.0.0/management/webui_props called" logs/obp-api.log
```
### 2. Count how many props were returned
```bash
grep "returning.*records" logs/obp-api.log | grep webui_props
```
### 3. See all property values for a specific call
```bash
# Get timestamp from entry log, then search around that time
grep "2025-01-15 10:23:45" logs/obp-api.log | grep "name: webui_"
```
### 4. Find specific property value
```bash
grep "name: webui_api_explorer_url" logs/obp-api.log
```
### 5. Monitor live calls
```bash
tail -f logs/obp-api.log | grep "webui_props"
```
### 6. Find errors related to webui props
```bash
grep -i "error" logs/obp-api.log | grep -i "webui"
grep "OBP-08" logs/obp-api.log # WebUI props error codes
```
### 7. Get all logs for a single request (if you know the timestamp)
```bash
grep "2025-01-15 10:23:45" logs/obp-api.log | grep -A 100 "webui_props called"
```
---
## Log Levels
All webui_props logging uses **INFO** level:
```scala
logger.info(s"========== GET /obp/v6.0.0/management/webui_props called with what=$what ==========")
logger.info(s"========== GET /obp/v6.0.0/management/webui_props returning ${result.size} records ==========")
logger.info(s" - name: ${prop.name}, value: ${prop.value}, webUiPropsId: ${prop.webUiPropsId}")
logger.info(s"========== END GET /obp/v6.0.0/management/webui_props ==========")
```
**Make sure your logging configuration includes INFO level for `code.api.v6_0_0.APIMethods600`**
---
## Code Reference
### Management Endpoint Logging
**File:** `obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala`
**Lines:** 3505, 3534-3540
```scala
logger.info(s"========== GET /obp/v6.0.0/management/webui_props called with what=$what ==========")
// ... processing ...
logger.info(s"========== GET /obp/v6.0.0/management/webui_props returning ${result.size} records ==========")
result.foreach { prop =>
logger.info(s" - name: ${prop.name}, value: ${prop.value}, webUiPropsId: ${prop.webUiPropsId}")
}
logger.info(s"========== END GET /obp/v6.0.0/management/webui_props ==========")
```
### Single Prop Endpoint
**File:** `obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala`
**Lines:** 3406-3438
**No explicit logging** - relies on standard API framework logging.
---
## Debugging Tips
### If you don't see logs:
1. **Check log level configuration:**
```properties
# In logback.xml or similar
<logger name="code.api.v6_0_0.APIMethods600" level="INFO"/>
```
2. **Verify the endpoint is being called:**
```bash
# Look for any v6.0.0 API calls
grep "v6.0.0" logs/obp-api.log
```
3. **Check for authentication errors:**
```bash
grep "canGetWebUiProps" logs/obp-api.log
```
4. **Look for the call in access logs:**
```bash
grep "management/webui_props" logs/access.log
```
### Common Issues
1. **No logs appear:**
- User doesn't have `CanGetWebUiProps` entitlement
- Wrong endpoint URL (check for typos: `webui_props` vs `webui-props`)
- Log level set too high (WARN or ERROR instead of INFO)
2. **Logs show 0 records:**
- No database props configured
- No config file props found
- Check `what` parameter value
3. **Property not found in logs:**
- Typo in property name (case-sensitive)
- Property not in database or config file
- Using wrong `what` parameter
---
## Summary
**To track webui_props API calls, search for:**
```bash
# Primary search patterns
grep "GET /obp/v6.0.0/management/webui_props called" logs/obp-api.log
grep "GET /obp/v6.0.0/management/webui_props returning" logs/obp-api.log
grep "name: webui_" logs/obp-api.log
grep "END GET /obp/v6.0.0/management/webui_props" logs/obp-api.log
# Single property endpoint (less logging)
grep "GET /obp/v6.0.0/webui-props/" logs/obp-api.log
# Errors
grep "OBP-08003" logs/obp-api.log
```
**The management endpoint has comprehensive logging showing:**
- When it was called
- What parameter was used (`what=active/database/config`)
- How many records returned
- Every single property name, value, and ID
- When processing completed

View File

@ -0,0 +1,221 @@
# WebUI Props v6.0.0 Improvements
## Summary
Enhanced the v6.0.0 `/webui-props` endpoint with better filtering, source tracking, and proper precedence handling.
## Changes Made
### 1. Fixed Endpoint Precedence in v6.0.0
**Problem:** v6.0.0 was using v5.1.0's `getWebUiProps` endpoint instead of its own because v5.1.0 routes were listed first.
**Solution:** Changed route ordering in `OBPAPI6_0_0.scala`:
```scala
// Before:
private val endpoints: List[OBPEndpoint] = endpointsOf5_1_0_without_root ++ endpointsOf6_0_0
// After:
private val endpoints: List[OBPEndpoint] = endpointsOf6_0_0.toList ++ endpointsOf5_1_0_without_root
```
**Result:** v6.0.0 endpoints now take precedence over earlier versions automatically.
### 2. Fixed `what=active` Logic
**Problem:** `what=active` was returning ALL props (database + config), creating duplicates when the same prop existed in both sources.
**Before:**
```scala
case "active" =>
val implicitWebUiPropsRemovedDuplicated = if(explicitWebUiProps.nonEmpty){
val duplicatedProps = explicitWebUiProps.map(explicitWebUiProp =>
implicitWebUiProps.filter(_.name == explicitWebUiProp.name)).flatten
implicitWebUiProps diff duplicatedProps
} else {
implicitWebUiProps.distinct
}
explicitWebUiProps ++ implicitWebUiPropsRemovedDuplicated
```
**After:**
```scala
case "active" =>
// Return one value per prop: database value if exists, otherwise config value
val databasePropNames = explicitWebUiPropsWithSource.map(_.name).toSet
val configPropsNotInDatabase = implicitWebUiProps.distinct.filterNot(prop =>
databasePropNames.contains(prop.name))
explicitWebUiPropsWithSource ++ configPropsNotInDatabase
```
**Result:** Returns ONE value per property name - database value if it exists, otherwise config value.
### 3. Added `source` Field to Track Prop Origin
**Problem:** Frontend had no way to know if a prop was editable (database) or read-only (config).
**Solution:** Added `source` field to `WebUiPropsCommons`:
```scala
case class WebUiPropsCommons(
name: String,
value: String,
webUiPropsId: Option[String] = None,
source: String = "database"
) extends WebUiPropsT with JsonFieldReName
```
Each prop now includes:
- `source="database"` for props stored in the database (editable via API)
- `source="config"` for props from configuration file (read-only)
### 4. Updated Documentation
Enhanced ResourceDoc descriptions to clarify:
- `what=active`: Returns one value per prop (database overrides config)
- `what=database`: Returns ONLY database props
- `what=config`: Returns ONLY config props
- Added `source` field explanation in response fields section
## Query Parameters
### GET /obp/v6.0.0/webui-props
**`what` parameter (optional, default: "active"):**
| Value | Behavior | Use Case |
|-------|----------|----------|
| `active` | One value per prop: database if exists, else config | Frontend display - get effective values |
| `database` | ONLY database-stored props | Admin UI - see what's been customized |
| `config` | ONLY config file defaults | Admin UI - see available defaults |
### GET /obp/v6.0.0/webui-props/{PROP_NAME}
**`active` parameter (optional boolean string, default: "false"):**
| Value | Behavior |
|-------|----------|
| `false` or omitted | Only database prop (fails if not in database) |
| `true` | Database prop, or fallback to config default |
## Response Format
```json
{
"webui_props": [
{
"name": "webui_api_explorer_url",
"value": "https://custom.example.com",
"webui_props_id": "550e8400-e29b-41d4-a716-446655440000",
"source": "database"
},
{
"name": "webui_hello_message",
"value": "Welcome to OBP",
"webui_props_id": "default",
"source": "config"
}
]
}
```
## Examples
### Get active props (effective values)
```bash
GET /obp/v6.0.0/webui-props
GET /obp/v6.0.0/webui-props?what=active
```
Returns all props with database values taking precedence over config defaults.
### Get only customized props
```bash
GET /obp/v6.0.0/webui-props?what=database
```
Shows which props have been explicitly set via API.
### Get only config defaults
```bash
GET /obp/v6.0.0/webui-props?what=config
```
Shows all available default values from `sample.props.template`.
### Get single prop with fallback
```bash
GET /obp/v6.0.0/webui-props/webui_api_explorer_url?active=true
```
Returns database value if exists, otherwise config default.
## Frontend Integration
The `source` field enables UIs to:
1. **Show edit buttons only for editable props:**
```javascript
if (prop.source === "database" || canCreateWebUiProps) {
showEditButton();
}
```
2. **Display visual indicators:**
```javascript
const icon = prop.source === "database" ? "custom" : "default";
const tooltip = prop.source === "database"
? "Custom value (editable)"
: "Default from config (read-only)";
```
3. **Prevent edit attempts on config props:**
```javascript
if (prop.source === "config") {
showWarning("This is a config default. Create a database override to customize.");
}
```
## Migration Notes
- **Backward Compatibility:** v5.1.0 and v3.1.0 endpoints unchanged
- **Default Value:** `source` defaults to `"database"` for backward compatibility
- **No Schema Changes:** Uses existing `WebUiPropsCommons` case class with new optional field
## Files Changed
1. `obp-api/src/main/scala/code/webuiprops/WebUiProps.scala`
- Added `source` field to `WebUiPropsCommons`
2. `obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala`
- Fixed `what=active` logic to return one value per prop
- Added `source` field to all WebUiPropsCommons instantiations
- Updated ResourceDoc for both endpoints
3. `obp-api/src/main/scala/code/api/v6_0_0/OBPAPI6_0_0.scala`
- Changed endpoint order to prioritize v6.0.0 over v5.1.0
## Testing
Test the different query modes:
```bash
# Get all active props (database + config, no duplicates)
curl http://localhost:8080/obp/v6.0.0/webui-props?what=active
# Get only database props
curl http://localhost:8080/obp/v6.0.0/webui-props?what=database
# Get only config props
curl http://localhost:8080/obp/v6.0.0/webui-props?what=config
# Get single prop (database only)
curl http://localhost:8080/obp/v6.0.0/webui-props/webui_api_explorer_url
# Get single prop with config fallback
curl http://localhost:8080/obp/v6.0.0/webui-props/webui_api_explorer_url?active=true
```
Verify that:
1. No duplicate property names in `what=active` response
2. Each prop includes `source` field
3. Database props have `source="database"`
4. Config props have `source="config"`
5. v6.0.0 endpoint is actually being called (check logs)

View File

@ -0,0 +1,276 @@
# WebUI Props Endpoint Visibility in API Explorer
## Question
**Why don't I see `/obp/v6.0.0/management/webui_props` in API Explorer II?**
---
## Answer
The endpoint **IS implemented** in v6.0.0, but it **requires authentication and a specific role**, which is why it may not appear in API Explorer II.
---
## Endpoint Details
### `/obp/v6.0.0/management/webui_props`
**File:** `obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala`
**Lines:** 3442-3498 (ResourceDoc), 3501-3542 (Implementation)
**Status:** ✅ **Implemented in v6.0.0**
**Authentication:** ✅ **Required** - Uses `authenticatedAccess(cc)`
**Authorization:** ✅ **Required** - Needs `CanGetWebUiProps` entitlement
**Tag:** `apiTagWebUiProps` (WebUi-Props)
**API Version:** `ApiVersion.v6_0_0`
---
## Why It's Not Visible in API Explorer II
### Reason 1: You're Not Logged In
API Explorer II may hide endpoints that require authentication when you're not logged in.
**Solution:** Log in to API Explorer II with a user account.
### Reason 2: You Don't Have the Required Role
The endpoint requires the `CanGetWebUiProps` entitlement.
**Code (line 3513):**
```scala
_ <- NewStyle.function.hasEntitlement("", u.userId, ApiRole.canGetWebUiProps, callContext)
```
**Solution:** Grant yourself the `CanGetWebUiProps` role.
### Reason 3: API Explorer II Filters
API Explorer II may filter endpoints based on:
- Tags
- Authentication requirements
- Your current roles/entitlements
- API version selection
**Solution:** Check API Explorer II filters and settings.
---
## How to Verify the Endpoint Exists
### 1. Check via Direct API Call
```bash
# Get an authentication token first
curl -X POST https://your-api.com/obp/v6.0.0/my/logins/direct \
-H "DirectLogin: username=YOUR_USERNAME, password=YOUR_PASSWORD, consumer_key=YOUR_CONSUMER_KEY"
# Then call the endpoint
curl -X GET https://your-api.com/obp/v6.0.0/management/webui_props \
-H "Authorization: DirectLogin token=YOUR_TOKEN"
```
### 2. Check the ResourceDoc Endpoint
```bash
# Get all resource docs for v6.0.0
curl https://your-api.com/obp/v6.0.0/resource-docs/obp
# Search for webui_props
curl https://your-api.com/obp/v6.0.0/resource-docs/obp | grep -i "webui_props"
```
### 3. Search Code
```bash
cd OBP-API
grep -r "management/webui_props" obp-api/src/main/scala/code/api/v6_0_0/
```
**Output:**
```
APIMethods600.scala: "/management/webui_props",
APIMethods600.scala: case "management" :: "webui_props":: Nil JsonGet req => {
```
---
## Required Role
### Role Name
`CanGetWebUiProps`
### How to Grant This Role
#### Via API (requires admin access)
```bash
POST /obp/v4.0.0/users/USER_ID/entitlements
{
"bank_id": "",
"role_name": "CanGetWebUiProps"
}
```
#### Via Database (for development)
```sql
-- Check if user has the role
SELECT * FROM entitlement
WHERE user_id = 'YOUR_USER_ID'
AND role_name = 'CanGetWebUiProps';
-- Grant the role (if needed)
INSERT INTO entitlement (entitlement_id, user_id, role_name, bank_id)
VALUES (uuid(), 'YOUR_USER_ID', 'CanGetWebUiProps', '');
```
---
## All WebUI Props Endpoints in v6.0.0
### 1. Get All WebUI Props (Management)
```
GET /obp/v6.0.0/management/webui_props
GET /obp/v6.0.0/management/webui_props?what=active
GET /obp/v6.0.0/management/webui_props?what=database
GET /obp/v6.0.0/management/webui_props?what=config
```
- **Authentication:** Required
- **Role:** `CanGetWebUiProps`
- **Tag:** `apiTagWebUiProps`
### 2. Get Single WebUI Prop (Public-ish)
```
GET /obp/v6.0.0/webui-props/WEBUI_PROP_NAME
GET /obp/v6.0.0/webui-props/WEBUI_PROP_NAME?active=true
```
- **Authentication:** NOT required (anonymous access)
- **Role:** None
- **Tag:** `apiTagWebUiProps`
**Example:**
```bash
# No authentication needed!
curl https://your-api.com/obp/v6.0.0/webui-props/webui_api_explorer_url?active=true
```
---
## Code References
### ResourceDoc Definition
**File:** `obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala`
**Lines:** 3442-3498
```scala
staticResourceDocs += ResourceDoc(
getWebUiProps,
implementedInApiVersion, // ApiVersion.v6_0_0
nameOf(getWebUiProps),
"GET",
"/management/webui_props",
"Get WebUiProps",
s"""...""",
EmptyBody,
ListResult("webui_props", ...),
List(
UserNotLoggedIn,
UserHasMissingRoles,
UnknownError
),
List(apiTagWebUiProps),
Some(List(canGetWebUiProps)) // ← ROLE REQUIRED
)
```
### Implementation
**File:** `obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala`
**Lines:** 3501-3542
```scala
lazy val getWebUiProps: OBPEndpoint = {
case "management" :: "webui_props":: Nil JsonGet req => {
cc => implicit val ec = EndpointContext(Some(cc))
val what = ObpS.param("what").getOrElse("active")
for {
(Full(u), callContext) <- authenticatedAccess(cc) // AUTH REQUIRED
...
_ <- NewStyle.function.hasEntitlement("", u.userId,
ApiRole.canGetWebUiProps, callContext) // ← ROLE CHECK
...
}
}
}
```
### Role Definition
**File:** `obp-api/src/main/scala/code/api/util/ApiRole.scala`
**Lines:** ~1300+
```scala
case class CanGetWebUiProps(requiresBankId: Boolean = false) extends ApiRole
lazy val canGetWebUiProps = CanGetWebUiProps()
```
---
## Comparison with Other Versions
### v3.1.0
- `GET /obp/v3.1.0/management/webui_props` - **Authentication + CanGetWebUiProps required**
### v5.1.0
- `GET /obp/v5.1.0/management/webui_props` - **No authentication required** (different implementation)
### v6.0.0
- `GET /obp/v6.0.0/management/webui_props` - **Authentication + CanGetWebUiProps required**
- `GET /obp/v6.0.0/webui-props/{NAME}` - **No authentication required** (new endpoint)
---
## Summary
| Aspect | Status | Details |
|--------|--------|---------|
| **Implemented in v6.0.0** | ✅ Yes | Line 3442-3542 in APIMethods600.scala |
| **Authentication Required** | ✅ Yes | Uses `authenticatedAccess(cc)` |
| **Role Required** | ✅ Yes | `CanGetWebUiProps` |
| **Tag** | `apiTagWebUiProps` | WebUi-Props category |
| **Why Not Visible** | Security | Hidden from non-authenticated users or users without role |
| **How to See It** | 1. Log in to API Explorer<br>2. Grant yourself `CanGetWebUiProps` role<br>3. Refresh API Explorer | |
---
## Alternative: Use the Public Endpoint
If you just want to **read** WebUI props without authentication, use the **single prop endpoint**:
```bash
# Public access - no authentication needed
curl https://your-api.com/obp/v6.0.0/webui-props/webui_api_explorer_url?active=true
```
This endpoint is available in v6.0.0 and does **NOT** require authentication or roles.
---
## Testing Commands
```bash
# 1. Check if you're logged in
curl https://your-api.com/obp/v6.0.0/users/current \
-H "Authorization: DirectLogin token=YOUR_TOKEN"
# 2. Check your roles
curl https://your-api.com/obp/v6.0.0/users/current \
-H "Authorization: DirectLogin token=YOUR_TOKEN" | grep -i "CanGetWebUiProps"
# 3. Try to call the endpoint
curl https://your-api.com/obp/v6.0.0/management/webui_props \
-H "Authorization: DirectLogin token=YOUR_TOKEN"
# If you get UserHasMissingRoles error, you need to grant yourself the role
# If you get 200 OK, the endpoint works!
```

View File

@ -24,6 +24,7 @@ import code.api.dynamic.entity.OBPAPIDynamicEntity
import code.apicollectionendpoint.MappedApiCollectionEndpointsProvider
import code.util.Helper
import code.util.Helper.{MdcLoggable, ObpS, SILENCE_IS_GOLDEN}
import net.liftweb.http.S
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.enums.ContentParam
import com.openbankproject.commons.model.enums.ContentParam.{ALL, DYNAMIC, STATIC}
@ -735,39 +736,124 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
|
|API_VERSION is the version you want documentation about e.g. v6.0.0
|
|You may filter this endpoint using the 'tags' url parameter e.g. ?tags=Account,Bank
|## Query Parameters
|
|(All endpoints are given one or more tags which for used in grouping)
|You may filter this endpoint using the following optional query parameters:
|
|You may filter this endpoint using the 'functions' url parameter e.g. ?functions=getBanks,bankById
|**tags** - Filter by endpoint tags (comma-separated list)
| Example: ?tags=Account,Bank or ?tags=Account-Firehose
| All endpoints are given one or more tags which are used for grouping
| Empty values will return error OBP-10053
|
|(Each endpoint is implemented in the OBP Scala code by a 'function')
|**functions** - Filter by function names (comma-separated list)
| Example: ?functions=getBanks,bankById
| Each endpoint is implemented in the OBP Scala code by a 'function'
| Empty values will return error OBP-10054
|
|**content** - Filter by endpoint type
| Values: static, dynamic, all (case-insensitive)
| static: Only show static/core API endpoints
| dynamic: Only show dynamic/custom endpoints
| all: Show both static and dynamic endpoints (default)
| Invalid values will return error OBP-10052
|
|**locale** - Language for localized documentation
| Example: ?locale=en_GB or ?locale=es_ES
| Supported locales: en_GB, es_ES, ro_RO
| Invalid locales will return error OBP-10041
|
|**api-collection-id** - Filter by API collection UUID
| Example: ?api-collection-id=4e866c86-60c3-4268-a221-cb0bbf1ad221
| Returns only endpoints belonging to the specified collection
| Empty values will return error OBP-10055
|
|This endpoint generates OpenAPI 3.1 compliant documentation with modern JSON Schema support.
|
|See the Resource Doc endpoint for more information.
|
| Note: Resource Docs are cached, TTL is ${GET_DYNAMIC_RESOURCE_DOCS_TTL} seconds
|Note: Resource Docs are cached, TTL is ${GET_DYNAMIC_RESOURCE_DOCS_TTL} seconds
|
|Following are more examples:
|## Examples
|
|Basic usage:
|${getObpApiRoot}/v6.0.0/resource-docs/v6.0.0/openapi
|
|Filter by tags:
|${getObpApiRoot}/v6.0.0/resource-docs/v6.0.0/openapi?tags=Account,Bank
|${getObpApiRoot}/v6.0.0/resource-docs/v6.0.0/openapi?tags=Account-Firehose
|
|Filter by content type:
|${getObpApiRoot}/v6.0.0/resource-docs/v6.0.0/openapi?content=static
|${getObpApiRoot}/v6.0.0/resource-docs/v6.0.0/openapi?content=dynamic
|
|Filter by functions:
|${getObpApiRoot}/v6.0.0/resource-docs/v6.0.0/openapi?functions=getBanks,bankById
|
|Combine multiple parameters:
|${getObpApiRoot}/v6.0.0/resource-docs/v6.0.0/openapi?content=static&tags=Account-Firehose
|${getObpApiRoot}/v6.0.0/resource-docs/v6.0.0/openapi?tags=Account,Bank,PSD2&functions=getBanks,bankById
|${getObpApiRoot}/v6.0.0/resource-docs/v6.0.0/openapi?content=static&locale=en_GB&tags=Account
|
|Filter by API collection:
|${getObpApiRoot}/v6.0.0/resource-docs/v6.0.0/openapi?api-collection-id=4e866c86-60c3-4268-a221-cb0bbf1ad221
|
""",
EmptyBody,
EmptyBody,
InvalidApiVersionString ::
ApiVersionNotSupported ::
InvalidLocale ::
InvalidContentParameter ::
InvalidTagsParameter ::
InvalidFunctionsParameter ::
InvalidApiCollectionIdParameter ::
UnknownError :: Nil,
List(apiTagDocumentation, apiTagApi)
)
/**
* OpenAPI 3.1 endpoint with comprehensive parameter validation.
*
* This endpoint generates OpenAPI 3.1 documentation with the following validated query parameters:
* - tags: Comma-separated list of tags to filter endpoints (e.g., ?tags=Account,Bank)
* - functions: Comma-separated list of function names to filter endpoints
* - content: Filter type - "static", "dynamic", or "all"
* - locale: Language code for localization (e.g., "en_GB", "es_ES")
* - api-collection-id: UUID to filter by specific API collection
*
* Parameter validation guards ensure:
* - Empty parameters (e.g., ?tags=) return 400 error
* - Invalid content values return 400 error with valid options
* - All parameters are properly trimmed and sanitized
*
* Examples:
* - ?content=static&tags=Account-Firehose
* - ?tags=Account,Bank&functions=getBanks,bankById
* - ?content=dynamic&locale=en_GB
*/
def getResourceDocsOpenAPI31 : OBPEndpoint = {
case "resource-docs" :: requestedApiVersionString :: "openapi" :: Nil JsonGet _ => {
cc => {
implicit val ec = EndpointContext(Some(cc))
val (resourceDocTags, partialFunctions, locale, contentParam, apiCollectionIdParam) = ResourceDocsAPIMethodsUtil.getParams()
// Early validation for empty parameters using underlying S to bypass ObpS filtering
if (S.param("tags").exists(_.trim.isEmpty)) {
Full(errorJsonResponse(InvalidTagsParameter, 400))
} else if (S.param("functions").exists(_.trim.isEmpty)) {
Full(errorJsonResponse(InvalidFunctionsParameter, 400))
} else if (S.param("api-collection-id").exists(_.trim.isEmpty)) {
Full(errorJsonResponse(InvalidApiCollectionIdParameter, 400))
} else {
val (resourceDocTags, partialFunctions, locale, contentParam, apiCollectionIdParam) = ResourceDocsAPIMethodsUtil.getParams()
for {
// Validate content parameter if provided
_ <- if (S.param("content").isDefined && contentParam.isEmpty) {
Helper.booleanToFuture(failMsg = InvalidContentParameter, cc = cc.callContext) {
false
}
} else {
Future.successful(true)
}
requestedApiVersion <- NewStyle.function.tryons(s"$InvalidApiVersionString Current Version is $requestedApiVersionString", 400, cc.callContext) {
ApiVersionUtils.valueOf(requestedApiVersionString)
}
@ -819,8 +905,9 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth
convertResourceDocsToOpenAPI31JvalueAndSetCache(cacheKey, requestedApiVersionString, resourceDocsJsonFiltered)
}
}
} yield {
(openApiJValue, HttpCode.`200`(cc.callContext))
} yield {
(openApiJValue, HttpCode.`200`(cc.callContext))
}
}
}
}
@ -980,7 +1067,7 @@ object ResourceDocsAPIMethodsUtil extends MdcLoggable{
case _ => Empty
}
def stringToContentParam (x: String) : Option[ContentParam] = x.toLowerCase match {
def stringToContentParam (x: String) : Option[ContentParam] = x.toLowerCase.trim match {
case "dynamic" => Some(DYNAMIC)
case "static" => Some(STATIC)
case "all" => Some(ALL)
@ -1000,14 +1087,18 @@ object ResourceDocsAPIMethodsUtil extends MdcLoggable{
case Empty => None
case _ => {
val commaSeparatedList : String = rawTagsParam.getOrElse("")
val tagList : List[String] = commaSeparatedList.trim().split(",").toList
val resourceDocTags =
for {
y <- tagList
} yield {
ResourceDocTag(y)
}
Some(resourceDocTags)
val tagList : List[String] = commaSeparatedList.trim().split(",").toList.filter(_.nonEmpty)
if (tagList.nonEmpty) {
val resourceDocTags =
for {
y <- tagList
} yield {
ResourceDocTag(y.trim())
}
Some(resourceDocTags)
} else {
None
}
}
}
logger.debug(s"tagsOption is $tags")
@ -1023,14 +1114,18 @@ object ResourceDocsAPIMethodsUtil extends MdcLoggable{
case Empty => None
case _ => {
val commaSeparatedList : String = rawPartialFunctionNames.getOrElse("")
val stringList : List[String] = commaSeparatedList.trim().split(",").toList
val pfns =
for {
y <- stringList
} yield {
y
}
Some(pfns)
val stringList : List[String] = commaSeparatedList.trim().split(",").toList.filter(_.nonEmpty)
if (stringList.nonEmpty) {
val pfns =
for {
y <- stringList
} yield {
y.trim()
}
Some(pfns)
} else {
None
}
}
}
logger.debug(s"partialFunctionNames is $partialFunctionNames")
@ -1047,7 +1142,8 @@ object ResourceDocsAPIMethodsUtil extends MdcLoggable{
val apiCollectionIdParam = for {
x <- ObpS.param("api-collection-id")
} yield x
if x.trim.nonEmpty
} yield x.trim
logger.debug(s"apiCollectionIdParam is $apiCollectionIdParam")

View File

@ -129,6 +129,11 @@ object ErrorMessages {
val createFxCurrencyIssue = "OBP-10050: Cannot create FX currency. "
val invalidLogLevel = "OBP-10051: Invalid log level. "
val InvalidContentParameter = "OBP-10052: Invalid content parameter. Valid values are: static, dynamic, all"
val InvalidTagsParameter = "OBP-10053: Invalid tags parameter. Tags cannot be empty when provided"
val InvalidFunctionsParameter = "OBP-10054: Invalid functions parameter. Functions cannot be empty when provided"
val InvalidApiCollectionIdParameter = "OBP-10055: Invalid api-collection-id parameter. API collection ID cannot be empty when provided"

View File

@ -3,7 +3,7 @@ package code.api.ResourceDocs1_4_0
import code.api.ResourceDocs1_4_0.ResourceDocs140.ImplementationsResourceDocs
import code.api.berlin.group.ConstantsBG
import code.api.util.APIUtil.OAuth._
import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn}
import code.api.util.ErrorMessages.{InvalidApiCollectionIdParameter, UserHasMissingRoles, UserNotLoggedIn}
import code.api.util.{ApiRole, CustomJsonFormats}
import code.api.v1_4_0.JSONFactory1_4_0.ResourceDocsJson
import code.setup.{DefaultUsers, PropsReset}
@ -100,6 +100,52 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with
//This should not throw any exceptions
responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description))
}
scenario("Test OpenAPI endpoint with valid parameters", ApiEndpoint1, VersionOfApi) {
val requestGetOpenAPI = (ResourceDocsV6_0Request / "resource-docs" / "v6.0.0" / "openapi").GET <<? List(("content", "static"), ("tags", "Account"))
val responseGetOpenAPI = makeGetRequest(requestGetOpenAPI)
responseGetOpenAPI.code should equal(200)
}
scenario("Test OpenAPI endpoint with invalid content parameter", ApiEndpoint1, VersionOfApi) {
val requestGetOpenAPI = (ResourceDocsV6_0Request / "resource-docs" / "v6.0.0" / "openapi").GET <<? List(("content", "invalid"))
val responseGetOpenAPI = makeGetRequest(requestGetOpenAPI)
responseGetOpenAPI.code should equal(400)
responseGetOpenAPI.body.toString should include("OBP-10052")
}
scenario("Test OpenAPI endpoint with empty tags parameter", ApiEndpoint1, VersionOfApi) {
val requestGetOpenAPI = (ResourceDocsV6_0Request / "resource-docs" / "v6.0.0" / "openapi").GET <<? List(("tags", ""))
val responseGetOpenAPI = makeGetRequest(requestGetOpenAPI)
responseGetOpenAPI.code should equal(400)
responseGetOpenAPI.body.toString should include("OBP-10053")
}
scenario("Test OpenAPI endpoint with empty functions parameter", ApiEndpoint1, VersionOfApi) {
val requestGetOpenAPI = (ResourceDocsV6_0Request / "resource-docs" / "v6.0.0" / "openapi").GET <<? List(("functions", ""))
val responseGetOpenAPI = makeGetRequest(requestGetOpenAPI)
responseGetOpenAPI.code should equal(400)
responseGetOpenAPI.body.toString should include("OBP-10054")
}
scenario("Test OpenAPI endpoint with valid multiple tags", ApiEndpoint1, VersionOfApi) {
val requestGetOpenAPI = (ResourceDocsV6_0Request / "resource-docs" / "v6.0.0" / "openapi").GET <<? List(("tags", "Account,Bank"), ("content", "static"))
val responseGetOpenAPI = makeGetRequest(requestGetOpenAPI)
responseGetOpenAPI.code should equal(200)
}
scenario("Test OpenAPI endpoint with Account-Firehose tag and static content", ApiEndpoint1, VersionOfApi) {
val requestGetOpenAPI = (ResourceDocsV6_0Request / "resource-docs" / "v6.0.0" / "openapi").GET <<? List(("content", "static"), ("tags", "Account-Firehose"))
val responseGetOpenAPI = makeGetRequest(requestGetOpenAPI)
responseGetOpenAPI.code should equal(200)
}
scenario("Test OpenAPI endpoint with empty api-collection-id parameter", ApiEndpoint1, VersionOfApi) {
val requestGetOpenAPI = (ResourceDocsV6_0Request / "resource-docs" / "v6.0.0" / "openapi").GET <<? List(("api-collection-id", ""))
val responseGetOpenAPI = makeGetRequest(requestGetOpenAPI)
responseGetOpenAPI.code should equal(400)
responseGetOpenAPI.body.toString should include(InvalidApiCollectionIdParameter)
}
scenario(s"We will test ${ApiEndpoint1.name} Api -v5.1.0", ApiEndpoint1, VersionOfApi) {
val requestGetObp = (ResourceDocsV5_0Request / "resource-docs" / "v5.1.0" / "obp").GET
val responseGetObp = makeGetRequest(requestGetObp)