Invitations
The invitation system in Waldur provides a mechanism for inviting users to join organizations (customers), projects, or other scoped resources with specific roles. The system supports two main invitation types: individual invitations and group invitations, with different workflows and approval mechanisms.
Architecture Overview
The invitation system is built around three core models in waldur_core.users.models:
- BaseInvitation: Abstract base class providing common fields and functionality
- Invitation: Individual invitations for specific users with email-based delivery
- GroupInvitation: Template-based invitations that can be used by multiple users matching specific criteria
- PermissionRequest: Approval workflow for group invitation requests
Invitation Types
Individual Invitations
Individual invitations are sent to specific email addresses and provide a direct mechanism to grant users access to resources.
Key Features
- Email-based delivery: Invitations are sent to specific email addresses
- Civil number validation: Optional civil number matching for enhanced security
- State management: Full lifecycle tracking with states like pending, accepted, canceled, expired
- Execution tracking: Background processing with error handling and retry capabilities
- Expiration handling: Automatic expiration based on configurable timeouts
- Webhook support: External system integration for invitation delivery
State Flow
stateDiagram-v2
[*] --> PENDING: Create invitation
[*] --> REQUESTED: Staff approval required
[*] --> PENDING_PROJECT: Project not active yet
REQUESTED --> PENDING: Staff approves
REQUESTED --> REJECTED: Staff rejects
PENDING_PROJECT --> PENDING: Project becomes active
PENDING --> ACCEPTED: User accepts
PENDING --> CANCELED: Creator cancels
PENDING --> EXPIRED: Timeout reached
CANCELED --> PENDING: Resend invitation
EXPIRED --> PENDING: Resend invitation
ACCEPTED --> [*]
REJECTED --> [*]
Group Invitations
Group invitations provide template-based access that multiple users can request to join, with an approval workflow. They support both private invitations (visible only to authenticated users with appropriate permissions) and public invitations (visible to all users including unauthenticated ones).
Key Features
- Pattern-based matching: Users can request access if they match email patterns or affiliations
- Approval workflow: Requests go through a review process before granting access
- Auto-approval: Optionally skip manual review for users matching invitation patterns
- Project creation option: Can automatically create projects instead of granting customer-level access
- Role mapping: Support for different roles at customer and project levels
- Template-based naming: Configurable project name templates for auto-created projects
- Public visibility: Public invitations can be viewed and requested by unauthenticated users
- Duplicate role prevention: Multiple layers of checks prevent duplicate role assignments
Workflow
sequenceDiagram
participant U as User
participant GI as GroupInvitation
participant PR as PermissionRequest
participant A as Approver
participant S as System
U->>GI: Submit request
GI->>GI: Check user already has role
GI->>GI: Check INVITATION_DISABLE_MULTIPLE_ROLES
GI->>GI: Check no existing PermissionRequest (pending/approved)
GI->>GI: Validate email/affiliation patterns
alt Validation fails
GI-->>U: 400 Bad Request
else Validation passes
GI->>PR: Create PermissionRequest
alt auto_approve enabled
PR->>PR: Auto-approve
PR->>S: Grant role (with duplicate guard)
PR-->>U: 200 OK (auto_approved: true)
else Manual approval
PR->>A: Notify approvers
A->>PR: Approve/Reject
alt Approved & auto_create_project
PR->>S: Create project (excludes soft-deleted)
S->>U: Grant project permission
else Approved & normal
PR->>S: Grant scope permission
end
PR->>U: Notify result
end
end
Public Group Invitations
Public group invitations are a special type of group invitation that can be viewed and requested by unauthenticated users. They are designed for open enrollment scenarios where organizations want to allow external users to request access to projects.
Key Characteristics
- Unauthenticated visibility: Listed in public API endpoints without authentication
- Staff-only creation: Only staff users can create and manage public invitations
- Project-level access only: Public invitations can only grant project-level roles, not customer-level roles
- Automatic project creation: All public invitations must use the auto-create project feature
- Enhanced security: Authentication is still required for submitting actual access requests
Constraints and Validation
- Staff authorization: Only
is_staff=Trueusers can create public group invitations - Auto-creation required: Public invitations must have
auto_create_project=True - Project roles only: Public invitations can only use roles starting with "PROJECT." (e.g.,
PROJECT.MANAGER,PROJECT.ADMIN) - No customer-level access: Cannot grant customer-level roles like
CUSTOMER.OWNERorCUSTOMER.SUPPORT
Use Cases
- Open research projects: Universities allowing external researchers to request project access
- Community initiatives: Organizations providing project spaces for community members
- Partner collaborations: Companies offering project access to external partners
- Educational platforms: Schools providing project environments for students
API Endpoints
Individual Invitations (/api/user-invitations/)
POST /api/user-invitations/- Create invitationGET /api/user-invitations/- List invitationsGET /api/user-invitations/{uuid}/- Retrieve invitation detailsPOST /api/user-invitations/{uuid}/send/- Resend invitationPOST /api/user-invitations/{uuid}/cancel/- Cancel invitationPOST /api/user-invitations/{uuid}/accept/- Accept invitation (authenticated)POST /api/user-invitations/{uuid}/delete/- Delete invitation (staff only)POST /api/user-invitations/approve/- Approve invitation (token-based)POST /api/user-invitations/reject/- Reject invitation (token-based)POST /api/user-invitations/{uuid}/check/- Check invitation validity (unauthenticated)GET /api/user-invitations/{uuid}/details/- Get invitation details for display
Group Invitations (/api/user-group-invitations/)
POST /api/user-group-invitations/- Create group invitation (authentication required)GET /api/user-group-invitations/- List group invitations (public invitations visible without authentication)GET /api/user-group-invitations/{uuid}/- Retrieve group invitation (public invitations accessible without authentication)POST /api/user-group-invitations/{uuid}/cancel/- Cancel group invitation (authentication required)POST /api/user-group-invitations/{uuid}/submit_request/- Submit access request (authentication required)GET /api/user-group-invitations/{uuid}/projects/- List available projects (authentication required)
Permission Requests (/api/user-permission-requests/)
GET /api/user-permission-requests/- List permission requestsGET /api/user-permission-requests/{uuid}/- Retrieve permission requestPOST /api/user-permission-requests/{uuid}/approve/- Approve requestPOST /api/user-permission-requests/{uuid}/reject/- Reject request
Model Fields and Relationships
BaseInvitation (Abstract)
1 2 3 4 5 6 7 | |
Invitation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
GroupInvitation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
PermissionRequest
1 2 3 4 5 6 7 8 9 | |
Permission System Integration
Access Control
Invitation management permissions are controlled through:
- Staff privileges: Staff users can manage all invitations
- Scope-based permissions: Users with CREATE permissions on scopes can manage invitations
- Customer-level access: Customer owners can manage invitations for their resources
- Hierarchical permissions: Customer permissions apply to contained projects
Permission Checks
The system uses can_manage_invitation_with() utility (src/waldur_core/users/utils.py:179) for authorization:
1 2 3 4 5 6 7 8 9 10 11 | |
Filtering and Visibility
- InvitationFilterBackend: Filters invitations based on user permissions
- GroupInvitationFilterBackend: Controls group invitation visibility, allows public invitations for unauthenticated users
- PendingInvitationFilter: Filters invitations user can accept
- VisibleInvitationFilter: Controls invitation detail visibility
Duplicate Role Prevention
The invitation system enforces multiple layers of protection against granting duplicate roles. These checks apply to both individual and group invitation flows.
Validation Layers
flowchart TD
A[User submits group invitation request] --> B{has_user check:<br/>User already has this role?}
B -->|Yes| R1[Reject: User already has this role in the scope]
B -->|No| C{INVITATION_DISABLE_MULTIPLE_ROLES<br/>and user has any role in scope?}
C -->|Yes| R2[Reject: User already has role within this scope]
C -->|No| D{Existing PermissionRequest<br/>pending or approved?}
D -->|Yes| R3[Reject: Permission request already exists for this scope]
D -->|No| E[Create PermissionRequest]
E --> F{Auto-approve enabled?}
F -->|Yes| G[Approve immediately]
F -->|No| H[Wait for manual approval]
G --> I{has_user guard in approve:<br/>User already has role?}
H --> I
I -->|Yes| S[Skip role creation silently]
I -->|No| J[Grant role via add_user]
Layer 1: has_user() Check in submit_request
Before creating a PermissionRequest, the system checks whether the user already holds the
exact role being requested in the target scope. This mirrors the check in individual invitation
acceptance (InvitationViewSet.accept()).
Layer 2: INVITATION_DISABLE_MULTIPLE_ROLES Check
When INVITATION_DISABLE_MULTIPLE_ROLES=True (Constance setting), a user cannot hold
any active role in the same scope. This prevents a user from accumulating multiple different
roles (e.g., both OWNER and SUPPORT) in a single customer or project. Applies to both
individual and group invitation flows.
Layer 3: Existing PermissionRequest Check
The system checks for existing PermissionRequest records in PENDING or APPROVED state
for the same user and scope. This prevents a user from submitting multiple requests even if
the first was auto-approved and already transitioned out of PENDING state.
Layer 4: Defense-in-Depth in approve()
The PermissionRequest.approve() method performs a final has_user() check before calling
add_user(). This catches edge cases where overlapping requests from different group
invitations target the same scope and role. If the user already has the role, approval
completes silently without creating a duplicate.
Soft-Deleted Project Handling
When auto_create_project=True, the system uses Project.available_objects.get_or_create()
instead of Project.objects.get_or_create(). This ensures soft-deleted projects (with
is_removed=True) are excluded, and a fresh project is created if the matching project was
previously deleted.
User Restrictions
Customers and Projects can define user restrictions that control which users can be added as members. These restrictions apply to both direct membership (via add_user API) and invitation acceptance. GroupInvitations can add additional restrictions on top of scope restrictions but cannot bypass them.
Restriction Fields
Both Customer and Project models support the following restriction fields:
1 2 3 4 5 6 7 8 9 | |
Validation Logic
Restrictions use OR logic within a field and AND logic across fields and levels:
- Within a field: User matches if ANY email pattern OR ANY affiliation OR ANY identity source matches
- Across fields: User must pass ALL fields that have restrictions set (e.g., if both email patterns and affiliations are set, user must match at least one of each)
- Across levels: User must pass ALL levels that have restrictions set (Customer → Project → GroupInvitation)
Special AAI validation rules:
- Nationalities: User must have at least one nationality in the allowed list (checks both
nationalityandnationalitiesfields) - Organization types: User's
organization_typemust be in the allowed list - Assurance levels: User must have ALL required assurance URIs (AND logic, not OR)
flowchart TD
A[User attempts to join] --> B{Customer has restrictions?}
B -->|Yes| C{User matches Customer restrictions?}
B -->|No| D{Project has restrictions?}
C -->|No| REJECT[Rejected - Customer restrictions not met]
C -->|Yes| D
D -->|Yes| E{User matches Project restrictions?}
D -->|No| F{GroupInvitation has restrictions?}
E -->|No| REJECT2[Rejected - Project restrictions not met]
E -->|Yes| F
F -->|Yes| G{User matches GroupInvitation restrictions?}
F -->|No| ALLOW[Allowed]
G -->|No| REJECT3[Rejected - GroupInvitation restrictions not met]
G -->|Yes| ALLOW
Cascade Validation Table
| Customer | Project | GroupInvitation | User Must Match |
|---|---|---|---|
| No restrictions | No restrictions | No restrictions | Anyone allowed |
| Has restrictions | No restrictions | No restrictions | Customer only |
| No restrictions | Has restrictions | No restrictions | Project only |
| Has restrictions | Has restrictions | No restrictions | Customer AND Project |
| Has restrictions | Has restrictions | Has restrictions | Customer AND Project AND GroupInvitation |
Permission to Set Restrictions
| Scope | Who Can Set Restrictions |
|---|---|
| Customer | Staff users only (is_staff=True) |
| Project | Users with CREATE_PROJECT permission on customer |
| GroupInvitation | Invitation creator (must respect scope restrictions) |
Examples
Customer-Level Email Restriction
1 2 3 4 5 6 | |
Project-Level Affiliation Restriction
1 2 3 4 5 6 | |
Identity Source Restriction
1 2 3 4 5 6 | |
Nationality Restriction (AAI)
1 2 3 4 5 6 | |
Organization Type Restriction (AAI)
1 2 3 4 5 6 7 8 9 | |
Assurance Level Restriction (AAI)
1 2 3 4 5 6 7 8 9 | |
Combined Customer and Project Restrictions
1 2 3 4 5 6 7 8 9 10 11 | |
Important Notes
- Staff users are NOT exempt: Restrictions apply to all users including staff
- Empty restrictions allow all: If no restrictions are set, any user is allowed
- GroupInvitation inherits scope restrictions: GroupInvitation cannot bypass Customer/Project restrictions
- Validation occurs at multiple points:
- Direct membership via
POST /customers/{uuid}/add_user/orPOST /projects/{uuid}/add_user/ - Invitation acceptance via
POST /invitations/{uuid}/accept/ - GroupInvitation request via
POST /group-invitations/{uuid}/submit_request/ - PermissionRequest approval
Background Processing
Celery Tasks
The invitation system uses several background tasks (src/waldur_core/users/tasks.py):
Core Processing Tasks
process_invitation: Main processing entry pointsend_invitation_created: Send invitation emails/webhooksget_or_create_user: Create user accounts for invitationssend_invitation_requested: Notify staff of invitation requests
Maintenance Tasks
cancel_expired_invitations: Clean up expired invitationscancel_expired_group_invitations: Clean up expired group invitationsprocess_pending_project_invitations: Activate invitations for started projectssend_reminder_for_pending_invitations: Send reminder emails
Notification Tasks
send_invitation_rejected: Notify creators of rejectionssend_mail_notification_about_permission_request_has_been_submitted: Notify approvers
Execution States
Individual invitations track background processing with FSM states:
SCHEDULED: Initial state, queued for processingPROCESSING: Currently being processedOK: Successfully processedERRED: Processing failed with error details
Error Handling
The system provides robust error tracking:
- Error messages: Human-readable error descriptions
- Error tracebacks: Full stack traces for debugging
- Retry mechanisms: Failed invitations can be resent
- Webhook failover: Falls back to email if webhooks fail
Configuration Options
Core Settings (WALDUR_CORE)
1 2 3 4 5 6 7 8 9 10 11 | |
Constance Settings
1 2 3 4 | |
Webhook Integration
1 2 3 4 5 6 | |
Email Templates
The system uses several email templates (waldur_core/users/templates/):
invitation_created- New invitation notificationinvitation_requested- Staff approval requestinvitation_rejected- Rejection notificationinvitation_expired- Expiration notificationinvitation_approved- Auto-created user credentialspermission_request_submitted- Permission request notification
Advanced Features
Auto-Approval
Group invitations with auto_approve=True skip manual review and immediately approve
matching users. When a user submits a request:
- The system validates patterns (email, affiliation, identity source)
- Creates a
PermissionRequestinPENDINGstate - Immediately transitions it to
APPROVED - Grants the role (subject to duplicate role prevention checks)
All duplicate role prevention layers still apply to auto-approved requests.
Project Auto-Creation
Group invitations can automatically create projects instead of granting customer-level access:
1 2 3 4 5 6 7 8 9 | |
Pattern Matching
Group invitations support sophisticated user matching:
1 2 3 4 5 6 7 8 9 10 11 | |
Token-Based Security
Staff approval uses cryptographically signed tokens:
1 2 3 4 5 6 | |
Security Considerations
Civil Number Validation
When civil_number is provided:
- Only users with matching civil numbers can accept invitations
- Provides additional security layer for sensitive resources
- Empty civil numbers allow any user to accept
Email Validation
Multiple levels of email validation:
- Loose matching (default): Case-insensitive email comparison
- Strict validation: Exact email matching when
ENABLE_STRICT_CHECK_ACCEPTING_INVITATION=True - Pattern matching: Group invitations validate against email patterns
Token Security
- Cryptographic signing: Uses Django's TimestampSigner
- Time-based expiration: Tokens expire after configurable period
- Payload validation: Validates UUID formats and user/invitation existence
- State verification: Ensures invitations are in correct state for operation
Permission Isolation
- Scope-based filtering: Users only see invitations they can manage
- Role validation: Ensures roles match scope content types, with additional constraints for public invitations
- Customer isolation: Prevents cross-customer invitation access
- Public invitation constraints: Public invitations restricted to project-level roles only
Best Practices
Creating Invitations
- Validate scope-role compatibility before creating invitations
- Set appropriate expiration times based on use case sensitivity
- Use civil numbers for high-security invitations
- Include helpful extra_invitation_text for user context
Group Invitation Setup
- Design clear email patterns that match intended user base
- Choose appropriate role mappings for auto-created projects
- Set meaningful project name templates for clarity
- Configure proper approval workflows with designated approvers
Public Invitation Management
- Restrict to staff users only - Only allow trusted staff to create public invitations
- Use project-level roles exclusively - Never grant customer-level access through public invitations
- Design clear project naming - Use descriptive templates since multiple projects may be created
- Monitor request volume - Public invitations may generate high volumes of access requests
- Set up proper approval processes - Ensure adequate staffing to handle public invitation approvals
Error Handling
- Monitor execution states for processing failures
- Set up alerts for invitation processing errors
- Provide clear error messages to users and administrators
- Implement retry strategies for transient failures
Performance Optimization
- Use bulk operations for large invitation batches
- Index frequently queried fields (email, state, customer)
- Archive old invitations to prevent table bloat
- Monitor background task queues for processing bottlenecks
Troubleshooting
Common Issues
- Invitations stuck in PROCESSING state
- Check Celery task processing
- Review error messages in invitation records
-
Verify SMTP/webhook configuration
-
Users can't accept invitations
- Verify email matching settings
- Check civil number requirements
-
Confirm invitation hasn't expired
-
Permission denied errors
- Validate user has CREATE permissions on scope
- Check customer-level permissions for hierarchical access
-
Confirm role is compatible with scope type
-
Group invitation requests not working
- Verify email patterns match user addresses
- Check affiliation matching logic
-
Confirm invitation is still active
-
"User already has this role" on group invitation submit
- User already holds the requested role in the target scope
- Check if user was previously granted the role via individual invitation or direct assignment
- If
INVITATION_DISABLE_MULTIPLE_ROLES=True, the user may hold a different role in the same scope
Debugging Tools
- Admin interface: View invitation details and states
- Celery monitoring: Track background task execution
- Logging: Enable debug logging for invitation processing
- API introspection: Use
/api/user-invitations/{uuid}/details/for status checking
Integration Examples
Basic Individual Invitation
1 2 3 4 5 6 7 8 9 10 11 | |
Group Invitation with Auto-Project
1 2 3 4 5 6 7 8 9 10 11 12 | |
Public Group Invitation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Webhook Integration
1 2 3 4 5 6 7 8 9 10 | |
This invitation system provides flexible, secure, and scalable user onboarding capabilities that integrate seamlessly with Waldur's permission and organizational structure.