ADR-016: MCP Security and Multi-Tenant Routing
Status
Proposed
Date
2025-12-06
Deciders
- iDempiere CLI maintainers
Context and Problem Statement
The iDempiere CLI exposes AI tools via MCP (Model Context Protocol) and LangChain4j for integration with Claude Code, Claude Desktop, and other AI clients. A critical security requirement is ensuring MCP users are properly authenticated and routed to the correct iDempiere tenant data.
iDempiere is a multi-tenant ERP system where tenant isolation is enforced through:
- AD_Client_ID: Identifies the tenant/company (e.g., GardenWorld=11, System=0)
- AD_Org_ID: Identifies the organization within a tenant
- AD_Role_ID: Controls what data and functions a user can access
- AD_User_ID: The authenticated user
Current State
The CLI currently has two data access paths:
-
Direct Database (JDBC): Used by
RegistryToolLogic,QueryToolLogic- Configured via environment variables:
IDEMPIERE_DB_* - Context from
application.properties:idempiere.defaults.ad-client-id - No authentication - relies on database credentials
- No tenant isolation - queries see all data unless SQL filtered
- Configured via environment variables:
-
REST API: Used by
GeneratedOpenApiFactory(ADR-009)- Requires running iDempiere server with REST plugin
- JWT tokens with embedded role/client context
- Tenant isolation enforced by iDempiere server
Two Main Use Cases
| Use Case | User Type | Access Scope | Security |
|---|---|---|---|
| 1. End User | Customer/External | Strictly scoped to ONE tenant (AD_Client_ID) | Must be isolated - cannot see other tenants |
| 2. Internal Staff | Employee/Admin | Various levels - from "All" to role-based | Access Rights determine visibility |
Critical Note: iDempiere database has NO built-in tenant isolation. All data lives in the same tables. Tenant isolation is enforced only at the Java application layer via Env.getCtx() context. MCP/CLI must replicate this security model.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Database (NO isolation) │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ C_Order: AD_Client_ID=0 | AD_Client_ID=11 | AD_Client_ID=12 | ... ││
│ │ (System) | (GardenWorld) | (Customer X) | ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌───────────────────────────────────┐ ┌───────────────────────────────────────┐
│ iDempiere Server (Java) │ │ idempiere-cli (MCP) │
│ ┌─────────────────────────────┐ │ │ ┌─────────────────────────────────┐ │
│ │ Env.getCtx() enforces: │ │ │ │ SecurityGuard must enforce: │ │
│ │ • AD_Client_ID │ │ │ │ • AD_Client_ID from token │ │
│ │ • AD_Org_ID │ │ │ │ • AD_Org_ID from token │ │
│ │ • AD_Role_ID │ │ │ │ • Query filter injection │ │
│ └─────────────────────────────┘ │ │ └─────────────────────────────────┘ │
└───────────────────────────────────┘ └───────────────────────────────────────┘
Use Case 1: End User (Strict Tenant Scope)
External customers or tenant-specific users:
- Token contains: Fixed AD_Client_ID (e.g., 11 = GardenWorld)
- Cannot access: Any data from other clients
- Role: Typically limited role (e.g., "Customer Portal")
- Enforcement: Every query MUST filter by AD_Client_ID
Use Case 2: Internal Staff (Variable Access)
Employees with role-based access:
- Token contains: AD_Role_ID determines access level
- Access Levels:
- System Admin: AD_Client_ID=0, can see all
- Client Admin: Specific client, all orgs
- Org User: Specific client + org only
- Enforcement: Query filters based on role's data access rules
Problem
When an MCP client (Claude Desktop, Claude Code) connects:
- How is the user authenticated?
- How is their tenant context (AD_Client_ID/AD_Org_ID) established?
- How is data isolation enforced?
- How do we prevent cross-tenant data access?
Decision Drivers
- Security: Must enforce tenant isolation - users can only access their tenant's data
- Compatibility: Work with existing iDempiere security model (AD_Role, AD_User)
- Usability: Minimal configuration for MCP clients
- Flexibility: Support both local development and production scenarios
- Auditability: All data access should be traceable to authenticated users
Considered Options
- Static Configuration - Single tenant per CLI instance
- Token-Based Multi-Tenant - REST API tokens determine tenant context
- Hybrid Approach - Static for direct DB, tokens for REST API
- Reuse iDempiere Core Security Classes - Adapt MRole, MUser in Quarkus
Decision Outcome
Chosen option: "Hybrid Approach" - different security models for different data access paths.
Security Model
┌─────────────────────────────────────────────────────────────────────────────┐
│ MCP Client (Claude Code) │
└──────────────────────────────┬──────────────────────────────────────────────┘
│ MCP Protocol (stdio/SSE)
┌──────────────────────────────▼──────────────────────────────────────────────┐
│ idempiere-cli MCP Server │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Security Guard Layer │ │
│ │ • Validate credentials on startup │ │
│ │ • Establish tenant context from config OR token │ │
│ │ • Enforce data access rules │ │
│ └──────────────────────────────────────┬──────────────────────────────────┘ │
│ │ │
│ ┌───────────────────┬──────────────────┴────────────────┬────────────────┐ │
│ │ Direct DB Path │ REST API Path │ Read-Only │ │
│ │ (Development) │ (Production) │ AD Queries │ │
│ └─────────┬─────────┴──────────────────┬────────────────┴───────┬────────┘ │
│ │ │ │ │
│ ┌─────────▼─────────┐ ┌───────────▼───────────┐ ┌─────────▼────────┐│
│ │ Static Context │ │ Token-Based Context │ │ No Tenant Filter ││
│ │ AD_Client_ID=env │ │ AD_Client_ID=JWT │ │ (AD_ tables only)││
│ │ AD_Org_ID=env │ │ AD_Org_ID=JWT │ │ ││
│ │ AD_User_ID=env │ │ AD_Role_ID=JWT │ │ ││
│ └─────────┬─────────┘ └───────────┬───────────┘ └─────────┬────────┘│
│ │ │ │ │
└────────────┼────────────────────────────┼────────────────────────┼──────────┘
│ │ │
┌────────────▼────────────┐ ┌────────────▼────────────┐ ┌────────▼─────────┐
│ PostgreSQL Database │ │ iDempiere REST API │ │ PostgreSQL DB │
│ (Development DB) │ │ (Token validation, │ │ (AD_Table only) │
│ │ │ security enforcement) │ │ │
└─────────────────────────┘ └─────────────────────────┘ └──────────────────┘
1. Direct Database Path Security
For direct DB access (QueryToolLogic, TableToolLogic):
# application.properties - Static tenant context
idempiere.defaults.ad-client-id=11 # GardenWorld
idempiere.defaults.ad-org-id=11 # HQ
idempiere.defaults.ad-user-id=100 # SuperUser
Security measures:
| Measure | Implementation |
|---|---|
| Read-only queries | Only SELECT allowed, no DDL/DML |
| Row limiting | Max 1000 rows per query |
| Query timeout | 30 seconds max |
| Sensitive masking | Password/token columns masked |
| SQL injection prevention | Pattern-based validation |
| Tenant filter injection | Auto-append AND AD_Client_ID = ? |
New: Automatic Tenant Filtering
/**
* Inject tenant filter into queries.
* Ensures users can only see their tenant's data.
*/
public String injectTenantFilter(String sql, int clientId) {
// For tables with AD_Client_ID column
if (hasClientColumn(sql)) {
return wrapWithTenantFilter(sql, clientId);
}
return sql;
}
// Example transformation:
// Input: SELECT * FROM C_Order WHERE DocStatus = 'CO'
// Output: SELECT * FROM C_Order WHERE DocStatus = 'CO' AND AD_Client_ID = 11
2. MCP User Authorization Flow
Users need a simple way to authorize MCP access. We provide a device authorization flow similar to GitHub CLI:
┌──────────────────────────────────────────────────────────────────────────────┐
│ MCP Authorization Flow (User Perspective) │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ STEP 1: User runs authorization command in Claude Code │
│ ─────────────────────────────────────────────────────────────────────────── │
│ │
│ Claude Code prompt: │
│ > Connect me to iDempiere │
│ │
│ CLI Response: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ To authorize iDempiere access, please: │ │
│ │ │ │
│ │ 1. Open this URL in your browser: │ │
│ │ http://localhost:8080/webui/mcp-auth │ │
│ │ │ │
│ │ 2. Login with your iDempiere credentials │ │
│ │ │ │
│ │ 3. Select Role and Organization │ │
│ │ │ │
│ │ 4. Copy the generated token and paste it below │ │
│ │ │ │
│ │ Waiting for token... │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ STEP 2: User opens URL and authenticates │
│ ─────────────────────────────────────────────────────────────────────────── │
│ │
│ Browser: http://localhost:8080/webui/mcp-auth │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ iDempiere MCP Authorization │ │
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
│ │ │ User: [admin@example.com ] │ │ │
│ │ │ Password: [•••••••• ] │ │ │
│ │ │ │ │ │
│ │ │ Role: [GardenWorld Admin ▼] │ │ │
│ │ │ Org: [HQ ▼] │ │ │
│ │ │ Warehouse:[HQ Warehouse ▼] │ │ │
│ │ │ │ │ │
│ │ │ [ Generate MCP Token ] │ │ │
│ │ └───────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ STEP 3: User copies token displayed │
│ ─────────────────────────────────────────────────────────────────────────── │
│ │
│ Browser shows: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ ✓ Token Generated Successfully │ │
│ │ │ │
│ │ Your MCP Token (copy this): │ │
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
│ │ │ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnQiOjExLC... │ │ │
│ │ └───────────────────────────────────────────────────────────────────┘ │ │
│ │ [Copy to Clipboard] │ │
│ │ │ │
│ │ Context: GardenWorld / HQ / GardenWorld Admin │ │
│ │ Expires: 2025-12-13 (7 days) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ STEP 4: User pastes token in Claude Code │
│ ─────────────────────────────────────────────────────────────────────────── │
│ │
│ Claude Code prompt: │
│ > eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnQiOjExLC... │
│ │
│ CLI Response: │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ ✓ Connected to iDempiere! │ │
│ │ │ │
│ │ Server: http://localhost:8080 │ │
│ │ Client: GardenWorld (11) │ │
│ │ Org: HQ (11) │ │
│ │ Role: GardenWorld Admin │ │
│ │ User: GardenAdmin │ │
│ │ │ │
│ │ Token saved to: ~/.idempiere-cli/tokens/localhost-8080.token │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
Alternative: Direct Token URL (for automation)
For users who prefer a direct URL:
GET http://localhost:8080/api/v1/auth/mcp-token?client=11&org=11&role=102
Response:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"client_id": 11,
"client_name": "GardenWorld",
"org_id": 11,
"org_name": "HQ",
"role_id": 102,
"role_name": "GardenWorld Admin",
"user_id": 100,
"user_name": "GardenAdmin",
"expires_at": "2025-12-13T00:00:00Z"
}
3. REST API Path Security
For REST API access (RestDataToolLogic per ADR-015):
Token-based authentication flow:
┌──────────────────────────────────────────────────────────────────────────────┐
│ Token Authentication Flow │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. User obtains token via MCP Auth Flow (above) or creates manually │
│ ┌─────────────────────────────────────────────┐ │
│ │ AD_AuthToken (stored in iDempiere) │ │
│ │ • AD_User_ID = 100 │ │
│ │ • AD_Role_ID = 102 │ │
│ │ • AD_Client_ID = 11 (from role) │ │
│ │ • AD_Org_ID = 11 (from role) │ │
│ │ • Token = "eyJhbGciOiJIUzI1NiIs..." │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 2. Token stored locally by CLI │
│ ┌─────────────────────────────────────────────┐ │
│ │ ~/.idempiere-cli/tokens/localhost-8080.token│ │
│ │ (encrypted, permissions 600) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 3. CLI sends requests with Bearer token │
│ ┌─────────────────────────────────────────────┐ │
│ │ HTTP Request │ │
│ │ Authorization: Bearer eyJhbGciOiJIUzI1NiIs│ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 4. iDempiere validates token & establishes context │
│ • Validates token exists and not expired │
│ • Sets AD_Client_ID, AD_Org_ID, AD_Role_ID, AD_User_ID │
│ • Enforces role-based data access │
│ • Returns only authorized data │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
Configuration for REST API:
# application.properties
idempiere.rest.url=http://localhost:8080/api/v1
idempiere.rest.token=${IDEMPIERE_REST_TOKEN:}
4. CLI Auth Commands
New commands for token management:
# Interactive login (opens browser for auth)
idempiere-cli auth login --server http://localhost:8080
# Login with token directly
idempiere-cli auth login --token eyJhbGciOiJIUzI1NiIs...
# Show current auth status
idempiere-cli auth status
# List saved tokens
idempiere-cli auth list
# Logout (remove token)
idempiere-cli auth logout --server localhost-8080
# Switch between saved tokens
idempiere-cli auth switch localhost-8080-gardenworld
Example session:
$ idempiere-cli auth login --server http://localhost:8080
Opening browser for authentication...
URL: http://localhost:8080/webui/mcp-auth
Paste your token here: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
✓ Authenticated successfully!
Server: http://localhost:8080
Client: GardenWorld (11)
Role: GardenWorld Admin
User: GardenAdmin
Token saved to: ~/.idempiere-cli/tokens/localhost-8080.token
$ idempiere-cli auth status
Current Authentication:
Server: http://localhost:8080
Client: GardenWorld (11)
Org: HQ (11)
Role: GardenWorld Admin
User: GardenAdmin
Expires: 2025-12-13 (7 days remaining)
5. Application Dictionary Queries
For AD queries (RegistryToolLogic) that access system metadata:
No tenant filter needed for:
AD_Table,AD_Column,AD_Reference(system-level metadata)- Read-only access to Application Dictionary
Tenant filter required for:
- Custom AD extensions (Entity Type filtering)
AD_Window,AD_Process(may have client-specific overrides)
6. MCP Client Configuration
Multi-tenant via separate configurations:
// ~/.claude/mcp.json
{
"mcpServers": {
"idempiere-gardenworld": {
"command": "java",
"args": ["-jar", "/path/to/idempiere-cli.jar", "--mcp"],
"env": {
"IDEMPIERE_REST_URL": "http://localhost:8080/api/v1",
"IDEMPIERE_REST_TOKEN": "${GARDENWORLD_TOKEN}",
"IDEMPIERE_DB_CLIENT_ID": "11"
}
},
"idempiere-system": {
"command": "java",
"args": ["-jar", "/path/to/idempiere-cli.jar", "--mcp"],
"env": {
"IDEMPIERE_REST_URL": "http://localhost:8080/api/v1",
"IDEMPIERE_REST_TOKEN": "${SYSTEM_TOKEN}",
"IDEMPIERE_DB_CLIENT_ID": "0"
},
"excluded": true
}
}
}
7. Security Guard Implementation
New SecurityGuard class to centralize security checks:
/**
* Security guard for MCP/AI tool access.
*
* <p>Enforces tenant isolation and access control for all data operations.</p>
*
* @see <a href="https://wiki.idempiere.org/en/Role">iDempiere Role Security</a>
*/
@ApplicationScoped
public class SecurityGuard {
@Inject
IdempiereConfig config;
@Inject
RestClientConfig restConfig;
/**
* Get the current security context.
*
* @return SecurityContext with tenant and user information
*/
public SecurityContext getContext() {
// For REST API: context comes from token validation
if (restConfig.isConfigured()) {
return getContextFromToken();
}
// For direct DB: context from static configuration
return getContextFromConfig();
}
/**
* Validate that an operation is allowed for the current context.
*
* @param operation the operation type (READ, WRITE, EXECUTE)
* @param resource the resource being accessed (table name, process)
* @throws SecurityException if access is denied
*/
public void validateAccess(Operation operation, String resource) {
SecurityContext ctx = getContext();
// Check if operation is allowed
if (!isOperationAllowed(operation, ctx)) {
throw new SecurityException("Operation not allowed: " + operation);
}
// Check resource access
if (!isResourceAllowed(resource, ctx)) {
throw new SecurityException("Access denied to: " + resource);
}
}
/**
* Inject tenant filter into SQL query.
*
* @param sql original query
* @return query with tenant filter
*/
public String injectTenantFilter(String sql) {
SecurityContext ctx = getContext();
return QuerySecurityFilter.apply(sql, ctx.getClientId(), ctx.getOrgId());
}
}
8. Data Access Levels
iDempiere has 7 data access levels that must be respected:
| Level | Code | Description | Tenant Filter |
|---|---|---|---|
| System Only | 4 | Only AD_Client_ID=0 | No |
| System+Client | 7 | System or any client | Client filter |
| Client Only | 6 | Client data only | Yes |
| Client+Organization | 3 | Client + Org filter | Yes + Org |
| Organization | 1 | Organization only | Yes + Org |
| All | 0 | No restrictions | No |
| Parent Organization | 2 | Parent org hierarchy | Yes + Hierarchy |
Implementation:
public enum DataAccessLevel {
SYSTEM_ONLY(4),
SYSTEM_PLUS_CLIENT(7),
CLIENT_ONLY(6),
CLIENT_PLUS_ORG(3),
ORG_ONLY(1),
ALL(0),
PARENT_ORG(2);
public boolean requiresClientFilter() {
return this != SYSTEM_ONLY && this != ALL;
}
public boolean requiresOrgFilter() {
return this == CLIENT_PLUS_ORG || this == ORG_ONLY || this == PARENT_ORG;
}
}
Confirmation
Implementation verification checklist:
Auth Commands
- [ ]
auth logincommand opens browser and accepts token - [ ]
auth statusshows current authentication context - [ ]
auth listshows all saved tokens - [ ]
auth logoutremoves token securely - [ ]
auth switchchanges active token - [ ] Tokens stored encrypted in
~/.idempiere-cli/tokens/
MCP Authorization Page (iDempiere side)
- [ ]
/webui/mcp-authpage implemented in iDempiere - [ ] Page shows role/org selection after login
- [ ] Token generated with proper context (AD_Client_ID, AD_Org_ID, AD_Role_ID)
- [ ] Token expiration configurable (default 7 days)
Security Guard
- [ ]
SecurityGuardclass created inorg.idempiere.cli.security - [ ]
QueryToolLogicusesSecurityGuard.injectTenantFilter() - [ ]
RestDataToolLogicvalidates token on each request - [ ] All MCP tools call
SecurityGuard.validateAccess()before operations
Testing
- [ ] Unit tests for tenant isolation (cross-tenant access denied)
- [ ] Integration test with GardenWorld token cannot see System data
- [ ] Token expiration handling tested
- [ ] Invalid token rejection tested
Documentation
- [ ] USER_GUIDE.md updated with auth commands
- [ ] Security configuration examples documented
Pros and Cons of the Options
Option 1: Static Configuration
Single tenant per CLI instance via environment variables.
- Good, because simple configuration
- Good, because works offline (no iDempiere server needed)
- Bad, because no dynamic tenant switching
- Bad, because credentials in environment (less secure)
- Bad, because must restart CLI to change tenant
Option 2: Token-Based Multi-Tenant
All access via REST API tokens with embedded context.
- Good, because iDempiere enforces security
- Good, because supports role-based access control
- Good, because audit trail via iDempiere logging
- Bad, because requires running iDempiere server
- Bad, because network latency for all operations
- Bad, because cannot work offline
Option 3: Hybrid Approach (CHOSEN)
Different security for different paths.
- Good, because flexible - development vs production
- Good, because direct DB for offline/development scenarios
- Good, because REST API for production with full security
- Good, because gradual migration path
- Neutral, because more complex to understand
- Bad, because two security models to maintain
Option 4: Reuse iDempiere Core Security Classes (Database Tools Only)
Adapt iDempiere's MRole, MUser, and security infrastructure to work in Quarkus.
> Note: This option applies only to direct database tools (QueryToolLogic, RegistryToolLogic). > REST API tools use iDempiere server's own security - no adaptation needed.
| Tool Path | Security Approach |
|---|---|
| Direct DB (QueryToolLogic) | Option 4: Adapt MRole logic in Quarkus |
| REST API (RestDataToolLogic) | iDempiere REST handles via token |
iDempiere Core Classes Available:
// org.compiere.model.MRole - Role-based access control
MRole role = MRole.get(ctx, AD_Role_ID);
// Key security methods:
role.addAccessSQL(sql, tableAlias, fullyQualified, rw); // Add WHERE clause
role.getWhereClause(tableName, rw); // Get security filter
role.isTableAccess(AD_Table_ID, ro); // Check table access
role.isColumnAccess(AD_Table_ID, AD_Column_ID, ro); // Check column access
role.getAD_Client_ID(); // Tenant context
role.getAD_Org_ID(); // Org context
How It Would Work:
┌─────────────────────────────────────────────────────────────────────────────┐
│ idempiere-cli (Quarkus) │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ SecurityContext (CDI Bean) │ │
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
│ │ │ MRole role = MRole.get(ctx, roleId); │ │ │
│ │ │ String whereClause = role.addAccessSQL(sql, "t", true, false); │ │ │
│ │ └───────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │ │
│ Uses iDempiere core classes from Maven dependency │
│ │ │
│ ┌─────────────────────────────────▼───────────────────────────────────────┐ │
│ │ <dependency> │ │
│ │ <groupId>org.idempiere</groupId> │ │
│ │ <artifactId>org.adempiere.base</artifactId> │ │
│ │ <version>12.0.0-SNAPSHOT</version> │ │
│ │ </dependency> │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Pros:
- Good, because proven security model (used in production)
- Good, because same logic as iDempiere server
- Good, because handles all edge cases (org hierarchy, document access, etc.)
- Good, because automatically updated when core changes
Cons:
- Bad, because heavy dependency (OSGi, many transitive deps)
- Bad, because
Env.getCtx()is thread-local, conflicts with Quarkus reactive - Bad, because requires database access to load AD_Role, AD_Org_Access
- Neutral, needs adapter layer to work outside OSGi container
Potential Approach: Lightweight Adapter
Instead of full MRole, create adapter that reads same AD tables:
@ApplicationScoped
public class RoleSecurityAdapter {
@Inject
AgroalDataSource dataSource;
/**
* Load role's access SQL from AD_Role, AD_Role_OrgAccess tables.
* Replicates MRole.addAccessSQL() logic without OSGi dependency.
*/
public String getAccessWhereClause(int roleId, String tableName) {
// Query AD_Role for IsAccessAllOrgs, UserLevel
// Query AD_Role_OrgAccess for org list
// Build WHERE clause: AD_Client_ID = ? AND AD_Org_ID IN (...)
}
}
More Information
iDempiere Security Architecture
iDempiere security is based on:
- Clients (Tenants): Complete data isolation between companies
- Organizations: Data sharing within a tenant
- Roles: Function and data access control
- Users: Individual accounts with role assignments
AD_Client (Tenant)
├── AD_Org (Organization)
│ ├── AD_User (User)
│ │ └── AD_User_Roles (Role assignments)
│ └── Data (filtered by org)
└── AD_Role (Role)
├── AD_Window_Access (Window permissions)
├── AD_Process_Access (Process permissions)
├── AD_Document_Action_Access (Doc actions)
└── AD_Role_OrgAccess (Org visibility)
REST Auth Token Creation
To create a REST Auth Token in iDempiere:
- Login to iDempiere as the target user
- Navigate to: System Admin > General Rules > Security > Rest Auth Token
- Create new record (token auto-generated)
- Copy token for CLI configuration
Reference: REST Web Services Documentation
Related ADRs
- ADR-009: OpenAPI REST Client - Generated API client
- ADR-013: LangChain4j Integration - AI tool framework
- ADR-014: Heng Sin's idempiere-mcp Evaluation - Multi-tenant reference
- ADR-015: REST Data Tools Facade - REST-based tools
References
- iDempiere Security Model
- iDempiere Data Access Level
- Heng Sin's idempiere-mcp - Multi-tenant token approach
- REST Web Services
- Model Context Protocol Security