feat: Plan-based feature permissions and quota enforcement
Backend: - Add HasQuota() permission factory for quota limits (resources, users, services, appointments, email templates, automated tasks) - Add HasFeaturePermission() factory for feature-based permissions (SMS, masked calling, custom domains, white label, plugins, webhooks, calendar sync, analytics) - Add has_feature() method to Tenant model for flexible permission checking - Add new tenant permission fields: can_create_plugins, can_use_webhooks, can_use_calendar_sync, can_export_data - Create Data Export API with CSV/JSON support for appointments, customers, resources, services - Create Analytics API with dashboard, appointments, revenue endpoints - Add calendar sync views and URL configuration Frontend: - Add usePlanFeatures hook for checking feature availability - Add UpgradePrompt components (inline, banner, overlay variants) - Add LockedSection wrapper and LockedButton for feature gating - Update settings pages with permission checks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
476
CALENDAR_SYNC_PERMISSION_IMPLEMENTATION.md
Normal file
476
CALENDAR_SYNC_PERMISSION_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,476 @@
|
||||
# Calendar Sync Permission Implementation
|
||||
|
||||
## Summary
|
||||
|
||||
Successfully added permission checking for the calendar sync feature in the Django backend. The implementation follows the existing `HasFeaturePermission` pattern and gates access to calendar OAuth and sync operations.
|
||||
|
||||
## Files Modified and Created
|
||||
|
||||
### Core Changes
|
||||
|
||||
#### 1. **core/models.py** - Tenant Model
|
||||
**File:** `/home/poduck/Desktop/smoothschedule2/smoothschedule/core/models.py`
|
||||
|
||||
Added new permission field to the Tenant model:
|
||||
```python
|
||||
can_use_calendar_sync = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Whether this business can sync Google Calendar and other calendar providers"
|
||||
)
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- New tenants will have `can_use_calendar_sync=False` by default
|
||||
- Platform admins can enable this per-tenant via the Django admin or API
|
||||
- Works with existing subscription plan system
|
||||
|
||||
#### 2. **core/migrations/0016_tenant_can_use_calendar_sync.py** - Database Migration
|
||||
**File:** `/home/poduck/Desktop/smoothschedule2/smoothschedule/core/migrations/0016_tenant_can_use_calendar_sync.py`
|
||||
|
||||
Database migration that adds the `can_use_calendar_sync` boolean field to the Tenant table.
|
||||
|
||||
**How to apply:**
|
||||
```bash
|
||||
cd /home/poduck/Desktop/smoothschedule2/smoothschedule
|
||||
docker compose -f docker-compose.local.yml exec django python manage.py migrate
|
||||
```
|
||||
|
||||
#### 3. **core/permissions.py** - Permission Check
|
||||
**File:** `/home/poduck/Desktop/smoothschedule2/smoothschedule/core/permissions.py`
|
||||
|
||||
Updated `HasFeaturePermission` factory function:
|
||||
- Added `'can_use_calendar_sync': 'Calendar Sync'` to `FEATURE_NAMES` mapping
|
||||
- This displays user-friendly error messages when the feature is not available
|
||||
- Follows the existing pattern used by other features (SMS reminders, webhooks, etc.)
|
||||
|
||||
**Usage Pattern:**
|
||||
```python
|
||||
from core.permissions import HasFeaturePermission
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
class MyViewSet(viewsets.ModelViewSet):
|
||||
permission_classes = [IsAuthenticated, HasFeaturePermission('can_use_calendar_sync')]
|
||||
```
|
||||
|
||||
#### 4. **core/oauth_views.py** - OAuth Permission Checks
|
||||
**File:** `/home/poduck/Desktop/smoothschedule2/smoothschedule/core/oauth_views.py`
|
||||
|
||||
Updated OAuth views to check calendar sync permission when initiating calendar-specific OAuth flows:
|
||||
|
||||
**GoogleOAuthInitiateView:**
|
||||
- Imported `HasFeaturePermission` from core.permissions
|
||||
- Added check: If `purpose == 'calendar'`, verify tenant has `can_use_calendar_sync` permission
|
||||
- Returns 403 Forbidden with upgrade message if permission denied
|
||||
- Email OAuth (`purpose == 'email'`) is NOT affected by this check
|
||||
|
||||
**MicrosoftOAuthInitiateView:**
|
||||
- Same pattern as Google OAuth
|
||||
- Supports both email and calendar purposes with respective permission checks
|
||||
|
||||
**Docstring updates:**
|
||||
Both views now document the permission requirements:
|
||||
```
|
||||
Permission Requirements:
|
||||
- For "email" purpose: IsPlatformAdmin only
|
||||
- For "calendar" purpose: Requires can_use_calendar_sync feature permission
|
||||
```
|
||||
|
||||
### New Calendar Sync Implementation
|
||||
|
||||
#### 5. **schedule/calendar_sync_views.py** - Calendar Sync Endpoints
|
||||
**File:** `/home/poduck/Desktop/smoothschedule2/smoothschedule/schedule/calendar_sync_views.py`
|
||||
|
||||
Created comprehensive calendar sync views with permission checking:
|
||||
|
||||
**CalendarSyncPermission Custom Permission:**
|
||||
- Combines authentication check with feature permission check
|
||||
- Used by all calendar sync endpoints
|
||||
- Ensures both user is authenticated AND tenant has permission
|
||||
|
||||
**CalendarListView (GET /api/calendar/list/)**
|
||||
- Lists connected calendars for the current tenant
|
||||
- Returns OAuth credentials with masked tokens
|
||||
- Protected by CalendarSyncPermission
|
||||
|
||||
**CalendarSyncView (POST /api/calendar/sync/)**
|
||||
- Initiates calendar event synchronization
|
||||
- Accepts credential_id, calendar_id, start_date, end_date
|
||||
- Verifies credential belongs to tenant
|
||||
- Checks credential validity before sync
|
||||
- TODO: Implement actual calendar API integration
|
||||
|
||||
**CalendarDeleteView (DELETE /api/calendar/disconnect/)**
|
||||
- Disconnects/revokes a calendar integration
|
||||
- Removes the OAuth credential
|
||||
- Logs the action for audit trail
|
||||
|
||||
**CalendarStatusView (GET /api/calendar/status/)**
|
||||
- Informational endpoint (authentication only, not feature-gated)
|
||||
- Returns whether calendar sync is enabled for tenant
|
||||
- Shows number of connected calendars
|
||||
- User-friendly message if feature not available
|
||||
|
||||
#### 6. **schedule/calendar_sync_urls.py** - URL Configuration
|
||||
**File:** `/home/poduck/Desktop/smoothschedule2/smoothschedule/schedule/calendar_sync_urls.py`
|
||||
|
||||
URL routes for calendar sync endpoints:
|
||||
```
|
||||
/api/calendar/status/ - Check calendar sync status
|
||||
/api/calendar/list/ - List connected calendars
|
||||
/api/calendar/sync/ - Sync calendar events
|
||||
/api/calendar/disconnect/ - Disconnect a calendar
|
||||
```
|
||||
|
||||
To integrate with main URL config, add to config/urls.py:
|
||||
```python
|
||||
path("calendar/", include("schedule.calendar_sync_urls", namespace="calendar")),
|
||||
```
|
||||
|
||||
#### 7. **schedule/tests/test_calendar_sync_permissions.py** - Test Suite
|
||||
**File:** `/home/poduck/Desktop/smoothschedule2/smoothschedule/schedule/tests/test_calendar_sync_permissions.py`
|
||||
|
||||
Comprehensive test suite with 20+ tests covering:
|
||||
|
||||
**CalendarSyncPermissionTests:**
|
||||
- `test_calendar_list_without_permission` - Verify 403 when disabled
|
||||
- `test_calendar_sync_without_permission` - Verify 403 when disabled
|
||||
- `test_oauth_calendar_initiate_without_permission` - Verify OAuth rejects calendar
|
||||
- `test_calendar_list_with_permission` - Verify 200 when enabled
|
||||
- `test_calendar_with_connected_credential` - Verify credential appears in list
|
||||
- `test_unauthenticated_calendar_access` - Verify 401 for anonymous users
|
||||
|
||||
**CalendarSyncIntegrationTests:**
|
||||
- `test_full_calendar_workflow` - Complete workflow (list → connect → sync → disconnect)
|
||||
|
||||
**TenantPermissionModelTests:**
|
||||
- `test_tenant_can_use_calendar_sync_default` - Verify default False
|
||||
- `test_has_feature_with_other_permissions` - Verify method works correctly
|
||||
|
||||
**Run tests:**
|
||||
```bash
|
||||
cd /home/poduck/Desktop/smoothschedule2/smoothschedule
|
||||
docker compose -f docker-compose.local.yml exec django pytest schedule/tests/test_calendar_sync_permissions.py -v
|
||||
```
|
||||
|
||||
#### 8. **CALENDAR_SYNC_INTEGRATION.md** - Integration Guide
|
||||
**File:** `/home/poduck/Desktop/smoothschedule2/smoothschedule/CALENDAR_SYNC_INTEGRATION.md`
|
||||
|
||||
Comprehensive developer guide including:
|
||||
- Architecture overview
|
||||
- Permission flow diagram
|
||||
- API endpoint examples with curl commands
|
||||
- Integration patterns with ViewSets
|
||||
- Testing examples
|
||||
- Security considerations
|
||||
- Related files reference
|
||||
|
||||
## Permission Flow
|
||||
|
||||
```
|
||||
User Request to Calendar Endpoint
|
||||
↓
|
||||
1. [Is User Authenticated?]
|
||||
├─ NO → 401 Unauthorized
|
||||
└─ YES ↓
|
||||
2. [Request Has Tenant Context?]
|
||||
├─ NO → 400 Bad Request
|
||||
└─ YES ↓
|
||||
3. [Does Tenant have can_use_calendar_sync?]
|
||||
├─ NO → 403 Forbidden (upgrade message)
|
||||
└─ YES ↓
|
||||
4. [Process Request]
|
||||
├─ Success → 200 OK
|
||||
└─ Error → 500 Server Error
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Permission Field Design
|
||||
|
||||
The `can_use_calendar_sync` field:
|
||||
- Is a BooleanField on the Tenant model
|
||||
- Defaults to False (disabled by default)
|
||||
- Can be set per-tenant by platform admins
|
||||
- Works alongside subscription_plan.permissions for more granular control
|
||||
- Integrates with existing `has_feature()` method on Tenant
|
||||
|
||||
### How Permission Checking Works
|
||||
|
||||
#### In OAuth Views
|
||||
```python
|
||||
# Check calendar sync permission if purpose is calendar
|
||||
if purpose == 'calendar':
|
||||
calendar_permission = HasFeaturePermission('can_use_calendar_sync')
|
||||
if not calendar_permission().has_permission(request, self):
|
||||
return Response({
|
||||
'success': False,
|
||||
'error': 'Your current plan does not include Calendar Sync...',
|
||||
}, status=status.HTTP_403_FORBIDDEN)
|
||||
```
|
||||
|
||||
#### In Calendar Sync Views
|
||||
```python
|
||||
class CalendarSyncPermission(IsAuthenticated):
|
||||
def has_permission(self, request, view):
|
||||
if not super().has_permission(request, view):
|
||||
return False
|
||||
|
||||
tenant = getattr(request, 'tenant', None)
|
||||
if not tenant:
|
||||
return False
|
||||
|
||||
return tenant.has_feature('can_use_calendar_sync')
|
||||
|
||||
class CalendarListView(APIView):
|
||||
permission_classes = [CalendarSyncPermission]
|
||||
```
|
||||
|
||||
### Separation of Concerns
|
||||
|
||||
- **Email OAuth**: Not affected by calendar sync permission (separate feature)
|
||||
- **Calendar OAuth**: Requires calendar sync permission only when `purpose='calendar'`
|
||||
- **Calendar Sync**: Requires calendar sync permission for all operations
|
||||
- **Calendar Status**: Authentication only (informational endpoint)
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Multi-Tenancy Isolation**
|
||||
- All OAuthCredential queries filter by tenant
|
||||
- Users can only access their own tenant's calendars
|
||||
- Credentials are not shared between tenants
|
||||
|
||||
2. **Token Security**
|
||||
- OAuth tokens stored encrypted at rest (via Django settings)
|
||||
- Tokens masked in API responses
|
||||
- Token validity checked before use
|
||||
|
||||
3. **CSRF Protection**
|
||||
- OAuth state parameter validated
|
||||
- Standard Django session handling
|
||||
|
||||
4. **Audit Trail**
|
||||
- All calendar operations logged with tenant/user info
|
||||
- Sync operations logged with timestamps
|
||||
- Disconnect operations logged
|
||||
|
||||
5. **Feature Gating**
|
||||
- Permission checked at view level
|
||||
- No way to bypass by direct API access
|
||||
- Consistent error messages for upgrade prompts
|
||||
|
||||
## API Examples
|
||||
|
||||
### Check if Feature is Available
|
||||
```bash
|
||||
GET /api/calendar/status/
|
||||
|
||||
# Response (if enabled):
|
||||
{
|
||||
"success": true,
|
||||
"can_use_calendar_sync": true,
|
||||
"total_connected": 2
|
||||
}
|
||||
|
||||
# Response (if disabled):
|
||||
{
|
||||
"success": true,
|
||||
"can_use_calendar_sync": false,
|
||||
"message": "Calendar Sync feature is not available for your plan"
|
||||
}
|
||||
```
|
||||
|
||||
### Initiate Calendar OAuth
|
||||
```bash
|
||||
POST /api/oauth/google/initiate/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"purpose": "calendar"
|
||||
}
|
||||
|
||||
# Response (if permission granted):
|
||||
{
|
||||
"success": true,
|
||||
"authorization_url": "https://accounts.google.com/o/oauth2/auth?..."
|
||||
}
|
||||
|
||||
# Response (if permission denied):
|
||||
{
|
||||
"success": false,
|
||||
"error": "Your current plan does not include Calendar Sync. Please upgrade..."
|
||||
}
|
||||
```
|
||||
|
||||
### List Connected Calendars
|
||||
```bash
|
||||
GET /api/calendar/list/
|
||||
|
||||
# Response:
|
||||
{
|
||||
"success": true,
|
||||
"calendars": [
|
||||
{
|
||||
"id": 1,
|
||||
"provider": "Google",
|
||||
"email": "user@gmail.com",
|
||||
"is_valid": true,
|
||||
"is_expired": false,
|
||||
"created_at": "2025-12-01T08:15:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Testing the Implementation
|
||||
|
||||
### Manual Testing via API
|
||||
|
||||
1. **Test without permission:**
|
||||
```bash
|
||||
# Create a user in a tenant without calendar sync
|
||||
curl -X GET http://lvh.me:8000/api/calendar/list/ \
|
||||
-H "Authorization: Bearer <token>"
|
||||
|
||||
# Expected: 403 Forbidden
|
||||
```
|
||||
|
||||
2. **Test with permission:**
|
||||
```bash
|
||||
# Enable calendar sync on tenant
|
||||
# Then try again:
|
||||
curl -X GET http://lvh.me:8000/api/calendar/list/ \
|
||||
-H "Authorization: Bearer <token>"
|
||||
|
||||
# Expected: 200 OK with calendar list
|
||||
```
|
||||
|
||||
### Run Test Suite
|
||||
```bash
|
||||
cd /home/poduck/Desktop/smoothschedule2/smoothschedule
|
||||
|
||||
# Run all calendar permission tests
|
||||
docker compose -f docker-compose.local.yml exec django pytest \
|
||||
schedule/tests/test_calendar_sync_permissions.py -v
|
||||
|
||||
# Run specific test
|
||||
docker compose -f docker-compose.local.yml exec django pytest \
|
||||
schedule/tests/test_calendar_sync_permissions.py::CalendarSyncPermissionTests::test_calendar_list_without_permission -v
|
||||
```
|
||||
|
||||
### Django Shell Testing
|
||||
```bash
|
||||
cd /home/poduck/Desktop/smoothschedule2/smoothschedule
|
||||
|
||||
docker compose -f docker-compose.local.yml exec django python manage.py shell
|
||||
|
||||
# In Django shell:
|
||||
from core.models import Tenant
|
||||
from smoothschedule.users.models import User
|
||||
|
||||
tenant = Tenant.objects.get(schema_name='demo')
|
||||
print(tenant.has_feature('can_use_calendar_sync')) # False initially
|
||||
|
||||
# Enable it
|
||||
tenant.can_use_calendar_sync = True
|
||||
tenant.save()
|
||||
|
||||
print(tenant.has_feature('can_use_calendar_sync')) # True now
|
||||
```
|
||||
|
||||
## Integration with Existing Systems
|
||||
|
||||
### Works with Subscription Plans
|
||||
```python
|
||||
# Tenant can get permission from subscription_plan.permissions
|
||||
subscription_plan.permissions = {
|
||||
'can_use_calendar_sync': True,
|
||||
'can_use_webhooks': True,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Works with Platform Admin Invitations
|
||||
```python
|
||||
# TenantInvitation can grant this permission
|
||||
invitation = TenantInvitation(
|
||||
can_use_calendar_sync=True,
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
### Works with User Role-Based Access
|
||||
- Permission is at tenant level, not user level
|
||||
- All users in a tenant with enabled feature can use it
|
||||
- Can be further restricted by user roles if needed
|
||||
|
||||
## Next Steps for Full Implementation
|
||||
|
||||
While the permission framework is complete, the following features need implementation:
|
||||
|
||||
1. **Google Calendar API Integration**
|
||||
- Fetch events from Google Calendar API using OAuth token
|
||||
- Map Google Calendar events to Event model
|
||||
- Handle recurring events
|
||||
- Sync deleted events
|
||||
|
||||
2. **Microsoft Calendar API Integration**
|
||||
- Fetch events from Microsoft Graph API
|
||||
- Handle Outlook calendar format
|
||||
|
||||
3. **Conflict Resolution**
|
||||
- Handle overlapping events from multiple calendars
|
||||
- Update vs. create decision logic
|
||||
|
||||
4. **Bi-directional Sync**
|
||||
- Push events back to calendar after scheduling
|
||||
- Handle edit/delete synchronization
|
||||
|
||||
5. **UI/Frontend Integration**
|
||||
- Calendar selection dialog
|
||||
- Sync status display
|
||||
- Calendar disconnect confirmation
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If needed to rollback:
|
||||
|
||||
1. **Revert database migration:**
|
||||
```bash
|
||||
docker compose -f docker-compose.local.yml exec django python manage.py migrate core 0015_tenant_can_create_plugins_tenant_can_use_webhooks
|
||||
```
|
||||
|
||||
2. **Revert code changes:**
|
||||
- Remove lines from core/models.py (can_use_calendar_sync field)
|
||||
- Remove calendar check from oauth_views.py
|
||||
- Remove calendar_sync_views.py
|
||||
- Remove calendar_sync_urls.py
|
||||
|
||||
3. **Revert permissions.py:**
|
||||
- Remove 'can_use_calendar_sync' from FEATURE_NAMES
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
| File | Type | Change |
|
||||
|------|------|--------|
|
||||
| core/models.py | Modified | Added can_use_calendar_sync field to Tenant |
|
||||
| core/migrations/0016_tenant_can_use_calendar_sync.py | New | Database migration |
|
||||
| core/permissions.py | Modified | Added can_use_calendar_sync to FEATURE_NAMES |
|
||||
| core/oauth_views.py | Modified | Added permission check for calendar OAuth |
|
||||
| schedule/calendar_sync_views.py | New | Calendar sync API views |
|
||||
| schedule/calendar_sync_urls.py | New | Calendar sync URL configuration |
|
||||
| schedule/tests/test_calendar_sync_permissions.py | New | Test suite (20+ tests) |
|
||||
| CALENDAR_SYNC_INTEGRATION.md | New | Integration guide |
|
||||
|
||||
## File Locations
|
||||
|
||||
All files are located in: `/home/poduck/Desktop/smoothschedule2/smoothschedule/`
|
||||
|
||||
**Key files:**
|
||||
- Models: `core/models.py` (line 194-197)
|
||||
- Migration: `core/migrations/0016_tenant_can_use_calendar_sync.py`
|
||||
- Permissions: `core/permissions.py` (line 354)
|
||||
- OAuth Views: `core/oauth_views.py` (lines 27, 92-98, 241-247)
|
||||
- Calendar Views: `schedule/calendar_sync_views.py` (entire file)
|
||||
- Calendar URLs: `schedule/calendar_sync_urls.py` (entire file)
|
||||
- Tests: `schedule/tests/test_calendar_sync_permissions.py` (entire file)
|
||||
- Documentation: `CALENDAR_SYNC_INTEGRATION.md`
|
||||
Reference in New Issue
Block a user