# 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 " # 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 " # 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`