CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-co-cask-cdap--cdap-security-spi

Service Provider Interface for CDAP's security and authorization framework enabling pluggable authorization mechanisms.

Pending
Overview
Eval results
Files

exceptions.mddocs/

Exception Handling

The CDAP Security SPI provides a structured exception hierarchy with HTTP status codes for proper error handling in web contexts and authorization scenarios.

Exception Hierarchy

All security-related exceptions implement HttpErrorStatusProvider to provide appropriate HTTP status codes for web applications.

UnauthorizedException

Runtime exception thrown when a Principal is not authorized to perform an Action on an EntityId.

class UnauthorizedException extends RuntimeException implements HttpErrorStatusProvider {
  /**
   * Create exception for single action authorization failure.
   */
  UnauthorizedException(Principal principal, Action action, EntityId entityId);
  
  /**
   * Create exception for multiple actions authorization failure.
   */
  UnauthorizedException(Principal principal, Set<Action> actions, EntityId entityId);
  
  /**
   * Create exception with cause for multiple actions authorization failure.
   */
  UnauthorizedException(Principal principal, Set<Action> actions, EntityId entityId, Throwable ex);
  
  /**
   * Create exception for entity access denial.
   */
  UnauthorizedException(Principal principal, EntityId entityId);
  
  /**
   * Create exception for conditional authorization failure.
   */
  UnauthorizedException(Principal principal, Set<Action> actions, EntityId entityId, boolean needHaveAll);
  
  /**
   * Create exception with custom message.
   */
  UnauthorizedException(String message);
  
  /**
   * Get HTTP status code.
   * 
   * @return HTTP_FORBIDDEN (403)
   */
  int getStatusCode();
}

AlreadyExistsException

Checked exception thrown when attempting to create a Role or entity that already exists.

class AlreadyExistsException extends Exception implements HttpErrorStatusProvider {
  /**
   * Create exception for role that already exists.
   */
  AlreadyExistsException(Role role);
  
  /**
   * Create exception with custom message.
   */
  AlreadyExistsException(String message);
  
  /**
   * Get HTTP status code.
   * 
   * @return HTTP_CONFLICT (409)
   */
  int getStatusCode();
}

BadRequestException

Checked exception thrown for invalid input scenarios.

class BadRequestException extends Exception implements HttpErrorStatusProvider {
  /**
   * Create exception for role-related bad request.
   */
  BadRequestException(Role role);
  
  /**
   * Create exception with custom message.
   */
  BadRequestException(String message);
  
  /**
   * Get HTTP status code.
   * 
   * @return HTTP_BAD_REQUEST (400)
   */
  int getStatusCode();
}

NotFoundException

Checked exception thrown when attempting to access unknown entities or roles.

class NotFoundException extends Exception implements HttpErrorStatusProvider {
  /**
   * Create exception for role not found.
   */
  NotFoundException(Role role);
  
  /**
   * Create exception with custom message.
   */
  NotFoundException(String message);
  
  /**
   * Get HTTP status code.
   * 
   * @return HTTP_NOT_FOUND (404)
   */
  int getStatusCode();
}

Usage Examples

Authorization Enforcement

public class MyAuthorizer extends AbstractAuthorizer {
  
  @Override
  public void enforce(EntityId entity, Principal principal, Set<Action> actions) 
      throws Exception {
    
    // Check if user exists
    if (!userExists(principal)) {
      throw new UnauthorizedException("Principal '" + principal.getName() + "' does not exist");
    }
    
    // Check each required action
    Set<Action> missingActions = new HashSet<>();
    for (Action action : actions) {
      if (!hasPermission(entity, principal, action)) {
        missingActions.add(action);
      }
    }
    
    // Throw exception if any actions are not permitted
    if (!missingActions.isEmpty()) {
      throw new UnauthorizedException(principal, missingActions, entity);
    }
  }
  
  @Override
  public void createRole(Role role) throws Exception {
    if (roleExists(role)) {
      throw new AlreadyExistsException(role);
    }
    
    // Create the role
    createRoleInBackend(role);
  }
  
  @Override
  public void dropRole(Role role) throws Exception {
    if (!roleExists(role)) {
      throw new NotFoundException(role);
    }
    
    // Delete the role
    deleteRoleFromBackend(role);
  }
}

Web Controller Error Handling

@RestController
public class AuthorizationController {
  
  @PostMapping("/roles")
  public ResponseEntity<String> createRole(@RequestBody Role role) {
    try {
      authorizer.createRole(role);
      return ResponseEntity.ok("Role created successfully");
      
    } catch (AlreadyExistsException e) {
      return ResponseEntity.status(e.getStatusCode())
          .body("Role already exists: " + e.getMessage());
          
    } catch (BadRequestException e) {
      return ResponseEntity.status(e.getStatusCode())
          .body("Invalid role data: " + e.getMessage());
    }
  }
  
  @DeleteMapping("/roles/{roleName}")
  public ResponseEntity<String> deleteRole(@PathVariable String roleName) {
    try {
      Role role = new Role(roleName);
      authorizer.dropRole(role);
      return ResponseEntity.ok("Role deleted successfully");
      
    } catch (NotFoundException e) {
      return ResponseEntity.status(e.getStatusCode())
          .body("Role not found: " + e.getMessage());
          
    } catch (Exception e) {
      return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
          .body("Error deleting role: " + e.getMessage());
    }
  }
  
  @PostMapping("/authorize")
  public ResponseEntity<String> checkAuthorization(
      @RequestBody AuthorizationRequest request) {
    
    try {
      authorizer.enforce(request.getEntity(), request.getPrincipal(), 
          request.getActions());
      return ResponseEntity.ok("Authorized");
      
    } catch (UnauthorizedException e) {
      return ResponseEntity.status(e.getStatusCode())
          .body("Access denied: " + e.getMessage());
          
    } catch (Exception e) {
      return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
          .body("Authorization check failed: " + e.getMessage());
    }
  }
}

Exception Handling with Detailed Messages

public class DetailedAuthorizationEnforcer implements AuthorizationEnforcer {
  
  @Override
  public void enforce(EntityId entity, Principal principal, Set<Action> actions) 
      throws Exception {
    
    List<String> errors = new ArrayList<>();
    
    // Validate principal
    if (principal == null || principal.getName() == null) {
      throw new UnauthorizedException("Principal cannot be null or have null name");
    }
    
    // Check if principal exists
    if (!principalExists(principal)) {
      throw new UnauthorizedException("Principal '" + principal.getName() + 
          "' of type " + principal.getType() + " does not exist");
    }
    
    // Check each action individually for detailed error reporting
    for (Action action : actions) {
      if (!hasPermission(entity, principal, action)) {
        errors.add("Missing permission for action: " + action.name());
      }
    }
    
    if (!errors.isEmpty()) {
      String detailedMessage = String.format(
          "Principal '%s' is not authorized to perform actions %s on entity '%s'. Reasons: %s",
          principal.getName(), actions, entity, String.join("; ", errors));
      
      throw new UnauthorizedException(detailedMessage);
    }
  }
  
  @Override
  public Set<? extends EntityId> isVisible(Set<? extends EntityId> entityIds, 
      Principal principal) throws Exception {
    
    if (principal == null) {
      throw new UnauthorizedException("Cannot check visibility with null principal");
    }
    
    return entityIds.stream()
        .filter(entity -> {
          try {
            // Check if principal has any permission on this entity
            return hasAnyPermission(entity, principal);
          } catch (Exception e) {
            // Log error but don't fail the entire operation
            logger.warn("Error checking visibility for entity {} and principal {}: {}", 
                entity, principal, e.getMessage());
            return false;
          }
        })
        .collect(Collectors.toSet());
  }
}

Global Exception Handler

@ControllerAdvice
public class SecurityExceptionHandler {
  
  @ExceptionHandler(UnauthorizedException.class)
  public ResponseEntity<ErrorResponse> handleUnauthorized(UnauthorizedException e) {
    ErrorResponse error = new ErrorResponse(
        "UNAUTHORIZED", 
        e.getMessage(), 
        System.currentTimeMillis()
    );
    
    return ResponseEntity.status(e.getStatusCode()).body(error);
  }
  
  @ExceptionHandler(AlreadyExistsException.class)
  public ResponseEntity<ErrorResponse> handleAlreadyExists(AlreadyExistsException e) {
    ErrorResponse error = new ErrorResponse(
        "ALREADY_EXISTS", 
        e.getMessage(), 
        System.currentTimeMillis()
    );
    
    return ResponseEntity.status(e.getStatusCode()).body(error);
  }
  
  @ExceptionHandler(NotFoundException.class)  
  public ResponseEntity<ErrorResponse> handleNotFound(NotFoundException e) {
    ErrorResponse error = new ErrorResponse(
        "NOT_FOUND", 
        e.getMessage(), 
        System.currentTimeMillis()
    );
    
    return ResponseEntity.status(e.getStatusCode()).body(error);
  }
  
  @ExceptionHandler(BadRequestException.class)
  public ResponseEntity<ErrorResponse> handleBadRequest(BadRequestException e) {
    ErrorResponse error = new ErrorResponse(
        "BAD_REQUEST", 
        e.getMessage(), 
        System.currentTimeMillis()
    );
    
    return ResponseEntity.status(e.getStatusCode()).body(error);
  }
  
  public static class ErrorResponse {
    private final String error;
    private final String message;
    private final long timestamp;
    
    public ErrorResponse(String error, String message, long timestamp) {
      this.error = error;
      this.message = message;
      this.timestamp = timestamp;
    }
    
    // Getters...
  }
}

Audit Logging with Exceptions

public class AuditingAuthorizer extends AbstractAuthorizer {
  private final AuditLogger auditLogger;
  
  @Override
  public void enforce(EntityId entity, Principal principal, Set<Action> actions) 
      throws Exception {
    
    long startTime = System.currentTimeMillis();
    
    try {
      // Perform authorization check
      performAuthorizationCheck(entity, principal, actions);
      
      // Log successful authorization
      auditLogger.logAuthorizationSuccess(principal, entity, actions, 
          System.currentTimeMillis() - startTime);
      
    } catch (UnauthorizedException e) {
      // Log authorization failure with details
      auditLogger.logAuthorizationFailure(principal, entity, actions, 
          e.getMessage(), System.currentTimeMillis() - startTime);
      
      // Re-throw the exception
      throw e;
      
    } catch (Exception e) {
      // Log unexpected errors
      auditLogger.logAuthorizationError(principal, entity, actions, 
          e.getClass().getSimpleName() + ": " + e.getMessage(), 
          System.currentTimeMillis() - startTime);
      
      // Re-throw the exception
      throw e;
    }
  }
}

Install with Tessl CLI

npx tessl i tessl/maven-co-cask-cdap--cdap-security-spi

docs

authentication.md

authorization.md

exceptions.md

index.md

tile.json