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>
477 lines
15 KiB
Markdown
477 lines
15 KiB
Markdown
# 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`
|