Security, Queries, and Specialized Extensions

 

Author: Eike Stepper

Security first authenticates a repository user and then authorizes that identity's operations. Query handling is a separate extension domain with a language/factory contract. Use these supported seams instead of general handlers for authentication, authorization, or a custom query language. Deployment credentials, password files, TLS, and directory-server configuration remain Operator's Guide topics.

1 Repository Protection
2 Permission Cache SPI
3 Authentication Example
4 Query Handlers
5 Extension Boundaries

1  Repository Protection

IRepositoryProtector protects a repository by combining a UserAuthenticator, authorization strategy, revision authorizers, and commit handlers. Authentication answers “which repository user presented these credentials?” Returning null rejects session opening; a returned identity becomes the user ID in the server session. Authorization answers “what may this identity do?” and is evaluated on repository reads and commits; authentication alone grants no access. Delegate credential verification to application policy rather than embedding password storage in this integration.

The authenticator establishes repository user identity; IRepositoryProtector.AuthorizationStrategy combines permissions; a IRepositoryProtector.RevisionAuthorizer decides revision permissions; and a IRepositoryProtector.CommitHandler runs before security checking and after a successful protected commit. In server XML, the repository's extension selects a product from IRepositoryProtector.PRODUCT_GROUP; its child elements select authenticator, authorization-strategy, revision-authorizer, and commit-handler products. This core repository-configurator extension is distinct from the optional security module. When org.eclipse.emf.cdo.server.security is installed, its application extension recognizes a separate element and creates the module's realm-backed security manager; an optional child selects its cache creator. See Element securityManager for the canonical manager and cache XML, and

cdo-server-repository.xml      
<?xml version="1.0" encoding="UTF-8"?>
<cdoServer>

  
<repository name="repo1">
    
<property name="overrideUUID" value=""/>
    
<property name="supportingAudits" value="true"/>
    
<property name="supportingBranches" value="true"/>
    
<property name="ensureReferentialIntegrity" value="false"/>
    
<property name="allowInterruptRunningQueries" value="true"/>
    
<property name="idGenerationLocation" value="STORE"/>
    
<property name="serializeCommits" value="false"/>
    
<property name="optimisticLockingTimeout" value="10000"/>

    
<store type="db">
      
<property name="connectionKeepAlivePeriod" value="60"/>
      
<property name="readerPoolCapacity" value="20"/>
      
<property name="writerPoolCapacity" value="20"/>

      
<mappingStrategy type="horizontal">
        
<property name="qualifiedNames" value="true"/>
      
</mappingStrategy>

      
<dbAdapter name="h2"/>

      
<dataSource
        class=
"org.h2.jdbcx.JdbcDataSource"
        URL=
"jdbc:h2:database/repo1"/>
    
</store>
  
</repository>

  
<!-- other acceptors and repositories -->

</cdoServer>

for the surrounding repository configuration. A custom protector product must be registered in its product group; applications do not implement IRepositoryProtector directly.

IPermissionManager is the legacy permission-management API used by the security model. The optional org.eclipse.emf.cdo.server.security module supplies its own ISecurityManager; use its public API when that module is intentionally part of an application, without coupling to its internal realm implementation.

2  Permission Cache SPI

The optional org.eclipse.emf.cdo.server.security module provides the PermissionCache and PermissionCacheFactory SPIs for applications that need an alternative resource-permission cache. Register a PermissionCacheFactory product in its managed-container product group, then select its type and optional description with repository properties security.permissionCache.type and security.permissionCache.description. The built-in factory type is default; its description can set creator capacity (for example |capacity=50000). The optional repository property security.permissionCache.default.capacity also supplies that capacity when no description suffix is present. Invalid or nonpositive capacity fails factory creation and therefore repository startup. The security extension also accepts one permissionCache child under securityManager; omitting its type selects the default cache. The default implementation has a configurable capacity, whose documented default is 100,000 entries. The internal security-manager setter is not an application configuration API.

Authorization evaluates resource, class, object, and global policy. The cache stores only a resource node's permission and the baseline inherited by ordinary contained objects; other policy checks remain separate. The security manager caches a resource baseline in two separate slots for each resource-node ID: permission on the resource node itself, and the baseline inherited by ordinary objects in that resource. The built-in resource permissions and resource filters can contribute to this baseline. Composed filters are cacheable only when their operands are cacheable; arbitrary class and package filters are not automatically treated as resource-stable. Custom Permission and PermissionFilter implementations are dynamic by default. The implementation has protected resource-cache classification hooks, but they are not a public application extension point; do not subclass an internal security-manager implementation to reach them. Keep custom policy dynamic, especially when it depends on request-local state, transactions, object contents, or AuthorizationContext.

Cached authorization applies only to reads at the current branch head. Historical reads and commit authorization remain uncached. Cache generations are isolated by repository, user, and branch, and relevant realm or resource-tree changes invalidate affected generations. A custom implementation must ensure that writes racing with invalidation cannot populate the replacement generation. This also applies independently to secondary repositories. A dynamic permission that depends on AuthorizationContext is evaluated outside the cached resource baseline on each authorization.

A custom PermissionCache.Creator must return a fresh logical generation for each repository/user/branch scope and keep late writes to an obsolete generation from becoming visible through its replacement. The cache implementation must support concurrent authorization access. The built-in cache's 100,000-entry capacity is shared by its creator's backing store; it is not a separate quota for every user or branch. Invalid capacity configuration prevents creator creation and therefore repository startup.

3  Authentication Example

An authenticator must delegate credential validation to application-owned policy and return null when the policy rejects the credentials. This example shows the integration seam without embedding credentials or choosing a password-storage scheme.

CreateAuthenticator.java      
return new UserAuthenticator()
{
  @Override
  public IRepositoryProtector.UserInfo authenticateUser(String userID, char[] password)
  {
    if (credentials.test(userID, password))
    {
      return new IRepositoryProtector.UserInfo(userID);
    }

    return null;
  }
};

4  Query Handlers

The client creates a query with a language identifier, query string, and named parameters. The server provider selects a factory by language; that factory creates an IQueryHandler, which receives query info and an IQueryContext carrying server view/session and branch-point context. Results flow through IQueryContext.addResult(Object); the handler stops when that method returns false or the context is cancelled. Register handler factories in QueryHandlerFactory.PRODUCT_GROUP; the factory type is the query language, so container configuration can select the handler. Implement IQueryHandler.PotentiallySlow when a handler can identify slow query forms. OCL is a supplied optional handler integration; this guide does not teach the OCL language itself. A custom language becomes reachable only after its factory is registered in the server container; the client's language string must match the factory type. A useful custom handler often adapts an application-owned, repository-aware index: parse a query string or parameter, find matching persistent IDs, and pass them to the context. The index must honor the query's branch and time semantics; a global current-state index is not correct for historical views. The handler must check cancellation during long scans and stop when IQueryContext.addResult(Object) returns false.

CreateProductLookupHandler.java      
return (info, context) -> {
  if (context.isCancelled())
  {
    return;
  }

  String productKey = info.getParameter("key");
  CDOID productID = findProductID.apply(productKey);
  if (productID != null && !context.isCancelled())
  {
    context.addResult(productID);
  }
};

RegisterProductLookupFactory.java      
container.registerFactory(new QueryHandlerFactory("product_lookup")
{
  @Override
  public IQueryHandler create(String description)
  {
    return createProductLookupHandler(findProductID);
  }
});

QueryProduct.java      
CDOQuery query = view.createQuery("product_lookup", "byKey");
query.setParameter("key", productKey);
query.setMaxResults(1);
return query.getResultValue(CDOID.class);

5  Extension Boundaries

Repository factories, query handler factories, repository-protector elements, DB adapters, and mapping strategies are container/factory extension mechanisms. They serve different responsibilities and should not be confused with application extensions. Use a supported factory SPI only when the application must supply that product; otherwise configure an existing product.