FileStorageService manages all file operations with security validation and integrity checks.
Architecture Overview
The file storage system consists of:- Base Directory: Configured via
CCDIGITAL_FS_BASE_PATHenvironment variable - Person Folders: One folder per person, named using normalized identity
- File Metadata: SHA-256 hash, size, and relative path stored in database
- Security Validation: Path traversal prevention and user access control
Configuration
Base Path Setup
Set Environment Variable
The
CCDIGITAL_FS_BASE_PATH environment variable defines the root directory for all file storage.This variable maps to both
ccdigital.fs.base-path and app.user-files-base-dir in application.properties.Properties Mapping
The base path is configured inapplication.properties:
FileStorageService
TheFileStorageService class (co.edu.unbosque.ccdigital.service.FileStorageService) handles all file operations.
Key Components
FileStorageProperties
Configuration class that loads
ccdigital.fs.base-path from environment.Location: co.edu.unbosque.ccdigital.config.FileStoragePropertiesFileStorageService
Main service for file operations.Location:
co.edu.unbosque.ccdigital.service.FileStorageServiceKey Methods:ensurePersonFolder(Person): Creates person folder if neededstorePersonFile(Person, MultipartFile): Saves file with metadataresolvePath(FileRecord): Resolves absolute path from relative pathloadAsResource(FileRecord): Loads file as Spring Resource
Folder Structure
Person Folder Naming
Each person gets a dedicated folder named using their identity: Algorithm (fromFileStorageService.buildPersonFolderName):
Concatenate Names
Combine
last_name + first_name from persons table.Example: “García” + “Juan” → “GarcíaJuan”Normalize to ASCII
Remove accents and diacritics using NFD normalization.Example: “GarcíaJuan” → “GarciaJuan”
| Last Name | First Name | Folder Name |
|---|---|---|
| García | Juan | GarciaJuan |
| Martínez | María | MartinezMaria |
| O’Neill | Patrick | ONeillPatrick |
| Müller | Hans | MullerHans |
Automatic Folder Creation
Folders are created automatically when:- Person is created via
PersonService.createPersonAndFolder() - First document is uploaded for a person
ensurePersonFolder() method ensures idempotent folder creation:
File Storage Process
When a document is uploaded:Validate Upload
- Check file is not empty
- Validate file extension (PDF required for issuer uploads)
- Verify MIME type
- Check PDF signature bytes (
%PDF) for PDFs
Store File
FileStorageService.storePersonFile() executes:- Ensure person folder exists
- Resolve target path:
basePath/PersonFolder/filename.pdf - Write file to disk (replaces if exists)
- Calculate SHA-256 hash of stored file
- Get file size in bytes
- Build relative path from base directory
Persist Metadata
Create
FileRecord entity with:storage_path: Relative path (e.g.,GarciaJuan/diploma.pdf)sha256: File integrity hashsize_bytes: File sizeversion: Auto-incremented bytrg_files_autoversiontriggeruploaded_at: Timestamp
person_documents table.File Metadata
All file metadata is stored in thefiles table:
Primary key.
Foreign key to
person_documents. Links file to document instance.Relative path from
basePath using / separator.Example: GarciaJuan/diploma.pdfAlways uses forward slash
/ regardless of OS, normalized when stored.SHA-256 hash of file content (hexadecimal).Purpose: Integrity verification, duplicate detection.
File size in bytes.
Auto-incremented version number per
person_document_id.Mechanism: trg_files_autoversion trigger automatically sets version.Upload timestamp.
Path Resolution
When accessing a file, the system resolves absolute paths from stored relative paths:Security Validation
CCDigital implements multiple security layers for file access:1. Path Traversal Prevention
All resolved paths are:- Normalized using
Path.normalize()to remove.and..components - Converted to absolute paths
- Validated against allowed base directories
2. User Access Validation
Before serving a file to an end user,UserDocsController validates:
Access Request Authorization
For issuer access, verify:
- Valid
access_requestsentry exists - Request status is
APPROVED - Access is within
valid_fromtovalid_untilwindow - User has consent via
consentstable
3. Document Review Status
Only documents withreview_status = 'APPROVED' can be:
- Included in access requests
- Downloaded by authorized users
- Synced to Fabric ledger
PENDING or REJECTED status are not accessible to non-admin users.
File Versioning
Thefiles table supports automatic versioning:
- Trigger:
trg_files_autoversion(BEFORE INSERT) - Behavior: Automatically sets
version = MAX(version) + 1for sameperson_document_id - Use Case: Track document updates/corrections over time
Storage Maintenance
Disk Space Monitoring
Monitor storage usage regularly:Orphan File Detection
Find files on disk not referenced in database:Integrity Verification
Verify file integrity using stored SHA-256 hashes:Backup Strategy
File System Backup
Back up the entire storage directory:
Database Backup
Back up file metadata from
files table:Performance Considerations
File System Performance
- Avoid deep nesting: Person folders are flat (one level deep)
- Use SSD storage: Recommended for production deployments
- Monitor I/O: Track read/write operations and latency
Database Queries
- Index on
storage_path: Speeds up file lookups - Index on
person_document_id: Optimizes versioning queries - Index on
sha256: Enables duplicate detection
Troubleshooting
Path Not Allowed Error
Problem: User gets “Path not allowed” when accessing documents. Cause: Mismatch betweenccdigital.fs.base-path and app.user-files-base-dir.
Solution:
File Not Found
Problem: Database has file record but file missing from disk. Cause: File deleted manually or backup restore incomplete. Solution:- Check file path:
SELECT storage_path FROM files WHERE id = ? - Verify physical file:
ls -l /var/ccdigital/storage/path/to/file.pdf - Restore from backup if missing
Permission Denied
Problem: Application cannot write to storage directory. Cause: Incorrect file system permissions. Solution:Hash Mismatch
Problem: File SHA-256 doesn’t match stored hash. Cause: File corruption or manual modification. Solution:- Identify affected file from integrity check script
- Restore from backup
- Update hash in database if file is known good:
Migration from Legacy Storage
If migrating from an older storage structure:Run Migration Script
Create migration script to:
- Copy files from legacy to new structure
- Update
storage_pathinfilestable - Verify SHA-256 hashes match
