Invite User To Project
Overview
The InviteUserToProject RPC method has been enhanced to include the following features:
- Google Group Allowlist Verification - Verifies invited users are in the early adopters allowlist
- Firebase Custom Claims Management - Adds project permissions to user's Firebase custom claims
- Optional Email Invitations - Sends invitation emails with project details and access links
Implementation Details
1. Google Group Membership Check
The implementation uses the existing AccessValidatorFactory to check if the invited user is a member of the codeproof-early-adopters@codetricks.org Google Group.
// Production (Cloud Run) - uses Application Default Credentials
boolean isInAllowlist = AccessValidatorFactory.getDefaultAccessValidator()
.hasAccess("", inviteeEmail);
// Local Development - uses explicit service account credentials file
boolean isInAllowlist = AccessValidatorFactory.getGoogleGroupsValidatorWithCredentialsFile(
".secrets/credentials/construction-code-expert-dev.google-groups-member-checker.json")
.hasAccess("", inviteeEmail);
Service Account Used: google-groups-member-checker@construction-code-expert-dev.iam.gserviceaccount.com
Required Permissions:
- Domain-wide delegation authority for
@codetricks.org - Google Directory API scope:
https://www.googleapis.com/auth/admin.directory.group.member.readonly
Environment Differences:
- Cloud Run: Application Default Credentials automatically use the associated service account
- Local Development: Must use explicit credentials file to avoid permission errors
Behavior:
- If user is NOT in allowlist → Invitation is rejected with informative error message
- If user IS in allowlist → Invitation proceeds to Firebase permissions step
2. Firebase Custom Claims Update with Project Validation
The implementation preserves existing user permissions and adds the new project role with validation:
// Step 1: Validate that the project actually exists
ProjectValidationService validationService = new ProjectValidationService();
if (!validationService.projectExists(architecturalProjectId)) {
// Reject invitation - project doesn't exist
return error;
}
// Step 2: Update Firebase custom claims with validation and user creation enabled
Map<String, String> inviteeProjects = new HashMap<>();
inviteeProjects.put(architecturalProjectId, role.name());
RBACService.setUserPermissions(inviteeEmail, inviteeProjects, true, true); // validateProjects=true, createUserIfNotExists=true
Features:
- Project Validation: Verifies architectural projects actually exist before granting permissions
- User Creation: Automatically creates Firebase users if they don't exist (optional)
- Data Integrity: Prevents orphaned permissions for non-existent projects
- Flexible Validation: Optional validation can be enabled/disabled as needed
- Permission Filtering: Can automatically filter out invalid projects
- Preserves existing project permissions
- Handles Google accounts that haven't signed into Firebase yet
- Updates user's custom claims with new project access
- Supports role updates (if user already has access to the project)
Enhanced Methods with Validation and User Creation:
// Standard method (no validation, no user creation)
RBACService.setUserPermissions(email, projects);
// Main method with both options - full control
RBACService.setUserPermissions(email, projects, validateProjects, createUserIfNotExists);
// Examples:
// With user creation only - creates Firebase user if they don't exist
RBACService.setUserPermissions(email, projects, false, true); // validateProjects=false, createUserIfNotExists=true
// With validation only - throws exception if any projects are invalid
RBACService.setUserPermissions(email, projects, true, false); // validateProjects=true, createUserIfNotExists=false
// With both validation and user creation
RBACService.setUserPermissions(email, projects, true, true); // validateProjects=true, createUserIfNotExists=true
// Convenience method for user creation only
RBACService.setUserPermissionsWithUserCreation(email, projects, true); // createUserIfNotExists=true
// Automatic filtering - removes invalid projects but continues
Map<String, String> validProjects = RBACService.setUserPermissionsWithFiltering(email, projects);
// Automatic filtering with user creation
Map<String, String> validProjects = RBACService.setUserPermissionsWithFiltering(email, projects, true); // createUserIfNotExists=true
3. Email Invitation Service
A new EmailService interface has been created with a simple logging implementation:
Files Created:
src/main/java/org/codetricks/auth/rbac/EmailService.java- Interfacesrc/main/java/org/codetricks/auth/rbac/SimpleEmailServiceImpl.java- Logging implementation
Email Content Includes:
- Inviter information
- Project details (ID and optional name)
- Role assignment and permissions description
- Direct link to access the project
- Contact information for questions
Current Implementation: Logs email content to console (perfect for development/testing)
Future Enhancement: Can be replaced with real email service (SendGrid, JavaMail, etc.)
4. Remove User From Project
The RemoveUserFromProject RPC method has been implemented to provide secure user access revocation:
Authorization: Only project OWNERS can remove users from projects
Process:
- Validate inputs - Check project ID and user email
- Authorization check - Verify caller is project owner
- Retrieve current permissions - Get user's existing project access
- Remove project access - Remove specific project from user's permissions
- Update Firebase claims - Apply reduced permissions to Firebase custom claims
- Return confirmation - Success response with details
Usage:
grpcurl \
-proto src/main/proto/rbac.proto \
-H "Authorization: Bearer $OWNER_JWT_TOKEN" \
-d '{
"architectural_project_id": "PROJECT-123",
"user_email": "user@codetricks.org"
}' \
${ESP_SERVICE_HOST}:443 \
org.codetricks.auth.rbac.RBACService/RemoveUserFromProject
Response Examples:
// Successful removal
{
"success": true,
"message": "User user@codetricks.org has been successfully removed from project PROJECT-123"
}
// User didn't have access (idempotent)
{
"success": true,
"message": "User user@codetricks.org does not have access to project PROJECT-123 (no removal needed)"
}
Security Features:
- ✅ Owner-only access - Only project owners can remove users
- ✅ Idempotent operation - Safe to call multiple times
- ✅ Audit logging - All removals are logged with details
- ✅ Firebase integration - Immediately updates user's access tokens
Files Modified/Created
Modified Files
src/main/java/org/codetricks/auth/rbac/RBACServiceImpl.java- Enhanced
inviteUserToProjectmethod with 3-step process - Added email service integration
- Added helper method for building project URLs
- Enhanced
New Files
src/main/java/org/codetricks/auth/rbac/EmailService.javasrc/main/java/org/codetricks/auth/rbac/SimpleEmailServiceImpl.javasrc/main/java/org/codetricks/auth/rbac/ProjectValidationService.javasrc/test/java/org/codetricks/auth/rbac/InviteUserToProjectTest.javasrc/test/java/org/codetricks/auth/rbac/ProjectValidationServiceTest.javadocs/RBAC_INVITE_USER_IMPLEMENTATION.md(this file)
Usage Example
Frontend (Angular)
// Example using real provisioned users
// Authenticated as admin@codetricks.org (inviter)
await this.rbacService.inviteUserToProject(
'ARCHITECTURAL-PROJECT-ID-2025-01-01', // Application architectural project, not GCP project
'contact@codetricks.org', // Real provisioned user
UserRole.PROMPTER // or 2 for PROMPTER role
);
Expected Flow
- Allowlist Check: Verifies
contact@codetricks.orgis incodeproof-early-adopters@codetricks.org - Permission Grant: Adds project access to user's Firebase custom claims
- Email Notification: Sends invitation email (currently logged to console)
Success Response
{
"success": true,
"message": "User contact@codetricks.org has been successfully invited to project ARCHITECTURAL-PROJECT-ID-2025-01-01 with role PROMPTER",
"invitationId": "ARCHITECTURAL-PROJECT-ID-2025-01-01_1642784400000"
}
Failure Response (Not in Allowlist)
{
"success": false,
"message": "User someuser@example.com is not in the allowlist Google Group (codeproof-early-adopters@codetricks.org). Please contact an administrator to be added to the allowlist before being invited to projects.",
"invitationId": ""
}
Testing
Run the test suite to verify functionality:
cd src
mvn test -Dtest=InviteUserToProjectTest
The test demonstrates:
- Google Group membership verification flow
- Firebase custom claims update process
- Email invitation content generation
- Error handling for non-allowlisted users
- Project validation functionality with simple test filesystem implementation
Test Implementation
The tests use a real filesystem approach:
- No mocking or test implementations - tests against actual project directories
- Real integration testing with the actual FileSystemHandler
- Self-contained tests that don't require external dependencies
- True filesystem validation using actual project existence checks
- Realistic test scenarios with real and invalid project combinations
Test Projects Used:
- Real Project Examples:
R2024.0091-2024-10-14,A.0155.01-2025-06-12(from Firebase examples) - Definitely Invalid:
DEFINITELY-NON-EXISTENT-PROJECT-12345,ANOTHER-INVALID-PROJECT-67890 - Test Project:
TEST-PROJECT-2025-01-01
This approach provides true validation against the actual filesystem, ensuring the validation service works correctly with real project data.
Test Users
The tests use real provisioned users from the @codetricks.org domain:
- Inviter:
admin@codetricks.org(should have admin/owner privileges) - Invitee:
contact@codetricks.org(should be in allowlist Google Group)
Automatic Firebase User Creation: The service automatically creates Firebase user records when the createUserIfNotExists option is enabled. Firebase will automatically link these records to Google OAuth when users sign in for the first time, handling the common scenario where Google accounts exist but users haven't signed into the Firebase application yet.
Integration Testing
The tests now include full integration testing against real infrastructure:
Prerequisites:
- Google Groups service account credentials file at:
.secrets/credentials/construction-code-expert-dev.google-groups-member-checker.json - Firebase Admin SDK initialized with actual credentials
- Valid Google accounts:
admin@codetricks.organdcontact@codetricks.org(Firebase user records will be created automatically if they don't exist) - Both users are members of
codeproof-early-adopters@codetricks.orgGoogle Group
What the Integration Test Does:
- Real Google Group Check: Calls actual Google Directory API to verify
contact@codetricks.orgmembership - Real Firebase Operations:
- Reads current custom claims for the user
- Adds a test project with PROMPTER role
- Verifies the update worked
- Restores original state (cleanup)
- End-to-End Validation: Tests the complete invitation flow logic
Running Integration Tests:
cd src
# Run all tests
mvn test -Dtest=InviteUserToProjectTest
# Run specific integration test
mvn test -Dtest=InviteUserToProjectTest#testRealInfrastructureIntegration
# Run Firebase claims test
mvn test -Dtest=InviteUserToProjectTest#testFirebaseCustomClaimsUpdate
Note: The tests now use specific service account credentials (not application default credentials) to avoid permission issues when testing locally. Tests include automatic cleanup to restore original Firebase state.
This provides complete confidence that the invitation system works with real Google Groups and Firebase infrastructure.
Configuration
Environment Variables
- No additional environment variables required for basic functionality
- Email service can be enhanced to use environment-specific configurations
Google Cloud Setup
- Ensure the Cloud Run instance is associated with the
google-groups-member-checkerservice account - Verify domain-wide delegation is properly configured for
@codetricks.org
Future Enhancements
Real Email Service Integration
Replace SimpleEmailServiceImpl with actual email service:
// Example with SendGrid
public class SendGridEmailServiceImpl implements EmailService {
private final SendGrid sendGrid;
public SendGridEmailServiceImpl(String apiKey) {
this.sendGrid = new SendGrid(apiKey);
}
@Override
public boolean sendProjectInvitationEmail(...) {
// Implement actual email sending
}
}
Project Metadata Integration
Enhance to fetch project names and additional metadata:
String projectName = ProjectMetadataService.getProjectName(architecturalProjectId);
Configurable URLs
Make project URLs environment-specific:
String baseUrl = System.getenv("PROJECT_BASE_URL");
String projectUrl = String.format("%s/projects/%s/pages/1/overview", baseUrl, architecturalProjectId);
Error Handling
The implementation includes comprehensive error handling:
- Google Group API Errors: Logged and returned as internal server errors
- Firebase Auth Errors: Logged with specific error messages
- Email Service Errors: Logged as warnings but don't fail the invitation
- Invalid Input: Validated with appropriate error messages
Security Considerations
- Only project owners can invite users (existing validation preserved)
- All users must be in the early adopters allowlist
- Firebase custom claims are securely managed through Firebase Admin SDK
- Email content doesn't expose sensitive project information
Project Validation Service
The new ProjectValidationService ensures data integrity by validating that architectural project IDs actually exist in the filesystem before granting permissions.
Core Functionality
ProjectValidationService validationService = new ProjectValidationService();
// Check if a single project exists
boolean exists = validationService.projectExists("PROJECT-ID-2025-01-01");
// Validate multiple projects and get list of invalid ones
List<String> invalidProjects = validationService.validateProjectIds(projectIds);
// Check if all projects in a permissions map are valid
boolean allValid = validationService.areAllProjectsValid(permissionsMap);
// Filter out invalid projects, keeping only valid ones
Map<String, String> validPermissions = validationService.filterValidProjects(permissionsMap);
Integration with RBAC
The enhanced RBAC service provides multiple levels of functionality:
- InviteUserToProject: Validates project exists before sending invitation
- setUserPermissions: Optional project validation and user creation
- setUserPermissionsWithFiltering: Automatically removes invalid projects with optional user creation
- User Creation: Automatically creates Firebase users for valid Google accounts when needed
Benefits
- Data Integrity: Prevents orphaned permissions for deleted or non-existent projects
- User Management: Seamlessly handles user invitations for Google accounts that haven't signed in yet
- Security: Ensures users can't gain permissions to invalid project IDs
- Flexibility: Can be used in strict mode (fails on invalid projects) or filtering mode (removes invalid projects)
- Performance: Uses filesystem checks which are fast and reliable
- Robustness: Eliminates "user not found" errors during invitation flows
Error Handling
- File System Errors: Gracefully handles IOException, treats as "project doesn't exist"
- Null/Empty Input: Safely handles null and empty project IDs
- Batch Operations: Efficiently validates multiple projects in a single call
Monitoring and Logging
All steps are logged with appropriate levels:
- INFO: Successful operations and normal flow
- WARNING: Non-critical issues (email failures, allowlist rejections)
- SEVERE: Critical errors that prevent invitation completion
Log format includes user emails, project IDs, and operation results for easy debugging and monitoring.