MATIH Platform is in active MVP development. Documentation reflects current implementation status.
3. Security & Multi-Tenancy
rbac
Permission Types

Permission Types

The MATIH Platform uses a string-based permission model with three dimensions: resource, action, and scope. Permissions are evaluated by the RbacService and PermissionEvaluator classes, which support wildcards, composite policies, and resource-level ownership checks. This page documents all permission types, the naming convention, and how they are evaluated.


Permission Format

Every permission in MATIH follows the resource:action format:

resource:action
ComponentDescriptionExamples
ResourceThe type of entity being accesseddata, queries, pipelines, reports, users, settings
ActionThe operation being performedread, write, execute, delete

Wildcard Permissions

The system supports three forms of wildcard:

PatternMeaningExample
*Full wildcard -- grants all permissions on all resourcesAssigned to super_admin
resource:*Resource wildcard -- grants all actions on a specific resourcedata:* grants read, write, delete on data
*:actionAction wildcard -- grants a specific action on all resources*:read grants read access to everything

Wildcard Resolution

The hasResourcePermission method checks all three levels:

// From RbacService.java
public boolean hasResourcePermission(String userId, String resource, String action) {
    String permission = resource + ":" + action;
    String wildcardResource = resource + ":*";
    String wildcardAction = "*:" + action;
 
    return hasAnyPermission(userId, permission, wildcardResource, wildcardAction);
}

The check evaluates in this order:

  1. Exact match: data:read
  2. Resource wildcard: data:*
  3. Action wildcard: *:read
  4. Global wildcard: *

Standard Resource Types

The platform defines the following standard resource types:

ResourceDescriptionAvailable Actions
dataData sources and datasetsread, write, delete
queriesSQL queries and saved queriesread, write, execute, delete
pipelinesData pipelines and jobsread, write, execute, delete
reportsDashboards and reportsread, write, delete, share
usersUser accounts and profilesread, write, delete
settingsTenant and service configurationread, write
auditAudit logs and compliance recordsread
data_qualityData quality rules and resultsread, write, execute
modelsML models and experimentsread, write, deploy, delete
agentsAI agents and workflowsread, write, execute, deploy, delete
connectorsData source connectorsread, write, test, delete
schedulesJob schedules and triggersread, write, delete

Standard Action Types

ActionDescriptionHTTP Method Mapping
readView or list resourcesGET
writeCreate or update resourcesPOST, PUT, PATCH
deleteRemove resourcesDELETE
executeRun or trigger resources (queries, pipelines, agents)POST (to execution endpoint)
deployDeploy to production (models, agents)POST (to deployment endpoint)
shareShare resources with other users or tenantsPOST (to sharing endpoint)
testTest connections or configurationsPOST (to test endpoint)

Resource-Level Policies

The PermissionEvaluator supports resource-level policies that go beyond simple role-based checks. These policies can inspect the actual resource instance to make authorization decisions.

ResourcePolicy Interface

// From PermissionEvaluator.java
public interface ResourcePolicy {
    boolean evaluate(String userId, String action, Object resource);
}

Ownership Policy

The most common resource-level policy is ownership, which restricts write and delete operations to the resource owner:

// From PermissionEvaluator.java
public static class OwnershipPolicy<T> implements ResourcePolicy {
    private final Function<T, String> ownerExtractor;
    private final Set<String> ownerOnlyActions;
 
    public OwnershipPolicy(Function<T, String> ownerExtractor) {
        this(ownerExtractor, Set.of("update", "delete"));
    }
 
    @Override
    @SuppressWarnings("unchecked")
    public boolean evaluate(String userId, String action, Object resource) {
        if (resource == null) {
            return true; // No instance to check
        }
 
        // Only enforce ownership for specific actions
        if (!ownerOnlyActions.contains(action)) {
            return true;
        }
 
        try {
            String ownerId = ownerExtractor.apply((T) resource);
            return userId.equals(ownerId);
        } catch (ClassCastException e) {
            return false;
        }
    }
}

Registering Ownership Policies

// Register an ownership policy for dashboards
evaluator.registerOwnershipPolicy("reports",
    (Dashboard dashboard) -> dashboard.getCreatedBy()
);
 
// Register an ownership policy for saved queries
evaluator.registerOwnershipPolicy("queries",
    (SavedQuery query) -> query.getOwnerId()
);

Composite Policies

Multiple policies can be composed using AllOfPolicy (all must pass) or AnyOfPolicy (any must pass):

// All policies must pass
ResourcePolicy strictPolicy = new PermissionEvaluator.AllOfPolicy(
    new OwnershipPolicy<>(Dashboard::getCreatedBy),
    (userId, action, resource) -> !((Dashboard) resource).isLocked()
);
 
// Any policy can pass
ResourcePolicy flexiblePolicy = new PermissionEvaluator.AnyOfPolicy(
    new OwnershipPolicy<>(Dashboard::getCreatedBy),
    (userId, action, resource) -> ((Dashboard) resource).isSharedWith(userId)
);

Tenant-Aware Permission Checks

The PermissionEvaluator automatically enforces tenant isolation when a resource implements the TenantAware interface:

// From PermissionEvaluator.java
public interface TenantAware {
    String getTenantId();
}

When evaluating permissions with a SecurityContext, the evaluator checks that the resource belongs to the same tenant as the requesting user:

public <T> boolean evaluate(SecurityContext context, String resource,
                            String action, T resourceInstance) {
    // ...
    // Tenant isolation check
    if (context.tenantId() != null && resourceInstance instanceof TenantAware tenantAware) {
        if (!context.tenantId().equals(tenantAware.getTenantId())) {
            return false; // Tenant isolation violation
        }
    }
 
    return evaluate(context.userId(), resource, action, resourceInstance);
}

This ensures that even if a user has the correct role-based permissions, they cannot access resources belonging to another tenant.


SecurityContext

The SecurityContext record carries the complete authorization context for a request:

// From PermissionEvaluator.java
public record SecurityContext(
    String userId,
    String tenantId,
    Set<String> roles,
    Map<String, Object> attributes
) {
    public static SecurityContext of(String userId, String tenantId) {
        return new SecurityContext(userId, tenantId, Set.of(), Map.of());
    }
 
    public static SecurityContext of(String userId, String tenantId, Set<String> roles) {
        return new SecurityContext(userId, tenantId, roles, Map.of());
    }
 
    public Optional<Object> getAttribute(String key) {
        return Optional.ofNullable(attributes.get(key));
    }
}

The attributes map can carry arbitrary context data for attribute-based access control (ABAC), such as:

AttributeTypeDescription
ip_addressStringClient IP for geo-restriction
mfa_verifiedBooleanWhether MFA was completed
session_idStringSession identifier for session-scoped policies
data_classificationStringData classification level for data-level policies

Permission Requirement Builders

The PermissionEvaluator provides a fluent API for creating reusable permission requirements as Predicate<SecurityContext>:

// Require a specific permission
Predicate<SecurityContext> canRead = evaluator.requirePermission("data:read");
 
// Require any of several permissions
Predicate<SecurityContext> canManage = evaluator.requireAnyPermission(
    "data:write", "data:delete"
);
 
// Require all permissions
Predicate<SecurityContext> canFullyManage = evaluator.requireAllPermissions(
    "data:read", "data:write", "data:delete"
);
 
// Require a specific role
Predicate<SecurityContext> isAdmin = evaluator.requireRole("tenant_admin");
 
// Require any role
Predicate<SecurityContext> isPrivileged = evaluator.requireAnyRole(
    "tenant_admin", "super_admin"
);

These predicates can be composed using standard Java functional interfaces:

// Must be admin AND have MFA verified
Predicate<SecurityContext> secureAdmin = isAdmin
    .and(ctx -> ctx.getAttribute("mfa_verified")
        .map(v -> Boolean.TRUE.equals(v))
        .orElse(false));

Related Pages