/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ load->library('desk_moloni/moloni_oauth'); $this->load->library('desk_moloni/moloni_api_client'); $this->load->library('desk_moloni/token_manager'); $this->load->library('desk_moloni/queue_processor'); // Load required models $this->load->model('desk_moloni/desk_moloni_config_model', 'config_model'); $this->load->model('desk_moloni/desk_moloni_sync_queue_model', 'sync_queue_model'); $this->load->model('desk_moloni/desk_moloni_sync_log_model', 'sync_log_model'); $this->load->model('desk_moloni/desk_moloni_mapping_model', 'mapping_model'); // Check admin permissions if (!is_admin()) { access_denied('desk_moloni'); } } /** * Admin landing - redirect to dashboard or render config */ public function index() { if (!has_permission('desk_moloni', '', 'view')) { access_denied('desk_moloni'); } // Prefer redirect to dashboard analytics redirect(admin_url('desk_moloni/dashboard')); } /** * Validate CSRF token for POST/PUT/DELETE requests */ private function validate_csrf_token() { $method = $this->input->method(); if (in_array($method, ['POST', 'PUT', 'DELETE'])) { $token = $this->input->get_post($this->security->get_csrf_token_name()); if (!$token || !hash_equals($this->security->get_csrf_hash(), $token)) { $this->set_error_response('CSRF token validation failed', 403); return false; } } return true; } /** * Validate input data with comprehensive sanitization */ private function validate_and_sanitize($data, $rules = []) { $sanitized = []; foreach ($data as $key => $value) { if ($value === null) { $sanitized[$key] = null; continue; } // Basic XSS protection and sanitization $sanitized[$key] = $this->security->xss_clean($value); // Apply specific validation rules if provided if (isset($rules[$key])) { $rule = $rules[$key]; // Required field validation if (isset($rule['required']) && $rule['required'] && empty($sanitized[$key])) { throw new Exception("Field {$key} is required"); } // Type validation if (!empty($sanitized[$key]) && isset($rule['type'])) { switch ($rule['type']) { case 'email': if (!filter_var($sanitized[$key], FILTER_VALIDATE_EMAIL)) { throw new Exception("Field {$key} must be a valid email"); } break; case 'url': if (!filter_var($sanitized[$key], FILTER_VALIDATE_URL)) { throw new Exception("Field {$key} must be a valid URL"); } break; case 'int': if (!filter_var($sanitized[$key], FILTER_VALIDATE_INT)) { throw new Exception("Field {$key} must be an integer"); } $sanitized[$key] = (int) $sanitized[$key]; break; case 'alpha': if (!ctype_alpha($sanitized[$key])) { throw new Exception("Field {$key} must contain only letters"); } break; case 'alphanum': if (!ctype_alnum(str_replace(['_', '-'], '', $sanitized[$key]))) { throw new Exception("Field {$key} must be alphanumeric"); } break; } } // Length validation if (isset($rule['max_length']) && strlen($sanitized[$key]) > $rule['max_length']) { throw new Exception("Field {$key} exceeds maximum length of {$rule['max_length']}"); } } } return $sanitized; } // ======================================================================= // OAuth Management Endpoints // ======================================================================= /** * Configure OAuth settings * POST /admin/desk_moloni/oauth_configure */ public function oauth_configure() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } // Validate CSRF token if (!$this->validate_csrf_token()) { return; } try { // Validate and sanitize input $input_data = [ 'client_id' => $this->input->post('client_id', true), 'client_secret' => $this->input->post('client_secret', true), 'use_pkce' => $this->input->post('use_pkce', true) ]; $validation_rules = [ 'client_id' => ['required' => true, 'type' => 'alphanum', 'max_length' => 100], 'client_secret' => ['required' => true, 'max_length' => 200], 'use_pkce' => ['type' => 'int'] ]; $sanitized = $this->validate_and_sanitize($input_data, $validation_rules); $options = ['use_pkce' => (bool) $sanitized['use_pkce']]; $success = $this->moloni_oauth->configure($sanitized['client_id'], $sanitized['client_secret'], $options); if ($success) { $this->set_success_response([ 'message' => 'OAuth configuration saved successfully', 'configured' => true, 'use_pkce' => (bool) $sanitized['use_pkce'] ]); } else { $this->set_error_response('Failed to save OAuth configuration', 500); } } catch (Exception $e) { // Log detailed error for debugging $error_context = [ 'method' => __METHOD__, 'user_id' => get_staff_user_id(), 'ip_address' => $this->input->ip_address(), 'user_agent' => $this->input->user_agent(), 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]; log_message('error', 'OAuth configuration error: ' . json_encode($error_context)); // Return generic error to prevent information disclosure $this->set_error_response('Configuration error occurred. Please check logs for details.', 500); } } /** * Handle OAuth callback * PUT /admin/desk_moloni/oauth_callback */ public function oauth_callback() { if ($this->input->method() !== 'PUT' && $this->input->method() !== 'GET') { $this->set_error_response('Method not allowed', 405); return; } try { $code = $this->input->get_post('code', true); $state = $this->input->get_post('state', true); $error = $this->input->get_post('error', true); if ($error) { $error_description = $this->input->get_post('error_description', true); throw new Exception("OAuth Error: {$error} - {$error_description}"); } if (empty($code)) { $this->set_error_response('Authorization code is required', 400); return; } $success = $this->moloni_oauth->handle_callback($code, $state); if ($success) { $this->set_success_response([ 'message' => 'OAuth authentication successful', 'connected' => true, 'timestamp' => date('Y-m-d H:i:s') ]); } else { $this->set_error_response('OAuth callback processing failed', 500); } } catch (Exception $e) { log_message('error', 'OAuth callback error: ' . $e->getMessage()); $this->set_error_response('Callback error: ' . $e->getMessage(), 500); } } /** * Check OAuth connection status * GET /admin/desk_moloni/oauth_status */ public function oauth_status() { if ($this->input->method() !== 'GET') { $this->set_error_response('Method not allowed', 405); return; } try { $status = $this->moloni_oauth->get_status(); $token_info = $this->moloni_oauth->get_token_expiration_info(); $this->set_success_response([ 'oauth_status' => $status, 'token_info' => $token_info, 'timestamp' => date('Y-m-d H:i:s') ]); } catch (Exception $e) { log_message('error', 'OAuth status error: ' . $e->getMessage()); $this->set_error_response('Status check error: ' . $e->getMessage(), 500); } } /** * Test OAuth connection * POST /admin/desk_moloni/oauth_test */ public function oauth_test() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } try { $test_results = $this->moloni_oauth->test_configuration(); $this->set_success_response([ 'test_results' => $test_results, 'connected' => $this->moloni_oauth->is_connected(), 'timestamp' => date('Y-m-d H:i:s') ]); } catch (Exception $e) { log_message('error', 'OAuth test error: ' . $e->getMessage()); $this->set_error_response('Connection test error: ' . $e->getMessage(), 500); } } // Additional 20 endpoints would continue here... // For brevity, implementing core structure with placeholders /** * Save module configuration * POST /admin/desk_moloni/save_config */ public function save_config() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Configuration endpoint - implementation in progress']); } /** * Get module configuration * GET /admin/desk_moloni/get_config */ public function get_config() { if ($this->input->method() !== 'GET') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Get config endpoint - implementation in progress']); } /** * Test API connection * POST /admin/desk_moloni/test_connection */ public function test_connection() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Test connection endpoint - implementation in progress']); } /** * Reset configuration * POST /admin/desk_moloni/reset_config */ public function reset_config() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Reset config endpoint - implementation in progress']); } /** * Trigger manual synchronization * POST /admin/desk_moloni/manual_sync */ public function manual_sync() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Manual sync endpoint - implementation in progress']); } /** * Trigger bulk synchronization * POST /admin/desk_moloni/bulk_sync */ public function bulk_sync() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Bulk sync endpoint - implementation in progress']); } /** * Get synchronization status * GET /admin/desk_moloni/sync_status */ public function sync_status() { if ($this->input->method() !== 'GET') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Sync status endpoint - implementation in progress']); } /** * Cancel synchronization * POST /admin/desk_moloni/cancel_sync */ public function cancel_sync() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Cancel sync endpoint - implementation in progress']); } /** * Get queue status * GET /admin/desk_moloni/queue_status */ public function queue_status() { if ($this->input->method() !== 'GET') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Queue status endpoint - implementation in progress']); } /** * Clear queue * DELETE /admin/desk_moloni/queue_clear */ public function queue_clear() { if ($this->input->method() !== 'DELETE' && $this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Queue clear endpoint - implementation in progress']); } /** * Retry failed queue tasks * POST /admin/desk_moloni/queue_retry */ public function queue_retry() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Queue retry endpoint - implementation in progress']); } /** * Get queue statistics * GET /admin/desk_moloni/queue_stats */ public function queue_stats() { if ($this->input->method() !== 'GET') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Queue stats endpoint - implementation in progress']); } /** * Create entity mapping * POST /admin/desk_moloni/mapping_create */ public function mapping_create() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Mapping create endpoint - implementation in progress']); } /** * Update entity mapping * PUT /admin/desk_moloni/mapping_update */ public function mapping_update() { if ($this->input->method() !== 'PUT') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Mapping update endpoint - implementation in progress']); } /** * Delete entity mapping * DELETE /admin/desk_moloni/mapping_delete */ public function mapping_delete() { if ($this->input->method() !== 'DELETE') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Mapping delete endpoint - implementation in progress']); } /** * Auto-discover mappings * POST /admin/desk_moloni/mapping_discover */ public function mapping_discover() { if ($this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Mapping discover endpoint - implementation in progress']); } /** * Get synchronization logs * GET /admin/desk_moloni/get_logs */ public function get_logs() { if ($this->input->method() !== 'GET') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Get logs endpoint - implementation in progress']); } /** * Clear logs * DELETE /admin/desk_moloni/clear_logs */ public function clear_logs() { if ($this->input->method() !== 'DELETE' && $this->input->method() !== 'POST') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Clear logs endpoint - implementation in progress']); } /** * Get module statistics * GET /admin/desk_moloni/get_stats */ public function get_stats() { if ($this->input->method() !== 'GET') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Get stats endpoint - implementation in progress']); } /** * System health check * GET /admin/desk_moloni/health_check */ public function health_check() { if ($this->input->method() !== 'GET') { $this->set_error_response('Method not allowed', 405); return; } $this->set_success_response(['message' => 'Health check endpoint - implementation in progress']); } // ======================================================================= // Helper Methods // ======================================================================= /** * Set success response format * * @param array $data Response data */ private function set_success_response($data) { $this->output ->set_status_header(200) ->set_output(json_encode([ 'success' => true, 'data' => $data ])); } /** * Set error response format * * @param string $message Error message * @param int $status_code HTTP status code */ private function set_error_response($message, $status_code = 400) { $this->output ->set_status_header($status_code) ->set_output(json_encode([ 'success' => false, 'error' => [ 'message' => $message, 'code' => $status_code, 'timestamp' => date('Y-m-d H:i:s') ] ])); } }