/** * Descomplicar® Crescimento Digital * https://descomplicar.pt */ load->library('desk_moloni/moloni_oauth'); $this->load->library('desk_moloni/token_manager'); $this->load->helper('url'); // Set page title $this->app_menu->add_breadcrumb(_l('desk_moloni'), admin_url('desk_moloni')); $this->app_menu->add_breadcrumb(_l('oauth_settings'), ''); } /** * OAuth settings and initiation page */ public function index() { // Handle form submission if ($this->input->post()) { $this->handle_oauth_configuration(); } $data = []; // Get current OAuth status $data['oauth_status'] = $this->moloni_oauth->get_status(); $data['token_status'] = $this->token_manager->get_token_status(); // Get configuration test results $data['config_test'] = $this->moloni_oauth->test_configuration(); // Get current settings $data['client_id'] = get_option('desk_moloni_client_id'); $data['client_secret'] = get_option('desk_moloni_client_secret'); $data['use_pkce'] = (bool)get_option('desk_moloni_use_pkce', true); // Generate CSRF token for forms $data['csrf_token'] = $this->security->get_csrf_hash(); // Load view $data['title'] = _l('desk_moloni_oauth_settings'); $this->load->view('admin/includes/header', $data); $this->load->view('admin/modules/desk_moloni/oauth_setup', $data); $this->load->view('admin/includes/footer'); } /** * Initiate OAuth authorization flow */ public function authorize() { try { // Verify CSRF token if (!$this->security->get_csrf_hash()) { throw new Exception('Invalid CSRF token'); } // Check if OAuth is configured if (!$this->moloni_oauth->is_configured()) { set_alert('danger', _l('oauth_not_configured')); redirect(admin_url('desk_moloni/oauth')); } // Generate state for CSRF protection $state = bin2hex(random_bytes(16)); $this->session->set_userdata('oauth_state', $state); // Get authorization URL $auth_url = $this->moloni_oauth->get_authorization_url($state); // Log authorization initiation log_activity('Desk-Moloni: OAuth authorization initiated by ' . get_staff_full_name()); // Redirect to Moloni OAuth page redirect($auth_url); } catch (Exception $e) { log_activity('Desk-Moloni: OAuth authorization failed - ' . $e->getMessage()); set_alert('danger', _l('oauth_authorization_failed') . ': ' . $e->getMessage()); redirect(admin_url('desk_moloni/oauth')); } } /** * Handle OAuth callback from Moloni */ public function callback() { try { // Get callback parameters $code = $this->input->get('code'); $state = $this->input->get('state'); $error = $this->input->get('error'); $error_description = $this->input->get('error_description'); // Handle OAuth errors if ($error) { throw new Exception("OAuth Error: {$error} - {$error_description}"); } // Validate required parameters if (empty($code)) { throw new Exception('Authorization code not received'); } // Verify state parameter (CSRF protection) $stored_state = $this->session->userdata('oauth_state'); if (empty($stored_state) || $state !== $stored_state) { throw new Exception('Invalid state parameter - possible CSRF attack'); } // Clear stored state $this->session->unset_userdata('oauth_state'); // Exchange code for tokens $success = $this->moloni_oauth->handle_callback($code, $state); if ($success) { // Log successful authentication log_activity('Desk-Moloni: OAuth authentication successful for ' . get_staff_full_name()); // Set success message set_alert('success', _l('oauth_connected_successfully')); // Test API connection $this->test_api_connection(); } else { throw new Exception('Token exchange failed'); } } catch (Exception $e) { log_activity('Desk-Moloni: OAuth callback failed - ' . $e->getMessage()); set_alert('danger', _l('oauth_callback_failed') . ': ' . $e->getMessage()); } // Redirect back to OAuth settings redirect(admin_url('desk_moloni/oauth')); } /** * Disconnect OAuth (revoke tokens) */ public function disconnect() { try { // Verify CSRF token if (!$this->input->post() || !$this->security->get_csrf_hash()) { throw new Exception('Invalid request'); } // Revoke OAuth access $success = $this->moloni_oauth->revoke_access(); if ($success) { log_activity('Desk-Moloni: OAuth disconnected by ' . get_staff_full_name()); set_alert('success', _l('oauth_disconnected_successfully')); } else { set_alert('warning', _l('oauth_disconnect_partial')); } } catch (Exception $e) { log_activity('Desk-Moloni: OAuth disconnect failed - ' . $e->getMessage()); set_alert('danger', _l('oauth_disconnect_failed') . ': ' . $e->getMessage()); } redirect(admin_url('desk_moloni/oauth')); } /** * Refresh OAuth tokens manually */ public function refresh_token() { try { // Verify CSRF token if (!$this->input->post() || !$this->security->get_csrf_hash()) { throw new Exception('Invalid request'); } // Attempt token refresh $success = $this->moloni_oauth->refresh_access_token(); if ($success) { log_activity('Desk-Moloni: OAuth tokens refreshed by ' . get_staff_full_name()); set_alert('success', _l('oauth_tokens_refreshed')); } else { set_alert('danger', _l('oauth_token_refresh_failed')); } } catch (Exception $e) { log_activity('Desk-Moloni: Token refresh failed - ' . $e->getMessage()); set_alert('danger', _l('oauth_token_refresh_failed') . ': ' . $e->getMessage()); } redirect(admin_url('desk_moloni/oauth')); } /** * Test OAuth configuration */ public function test_config() { try { // Run configuration test $test_results = $this->moloni_oauth->test_configuration(); if ($test_results['is_valid']) { set_alert('success', _l('oauth_config_test_passed')); } else { $issues = implode('
', $test_results['issues']); set_alert('danger', _l('oauth_config_test_failed') . ':
' . $issues); } } catch (Exception $e) { set_alert('danger', _l('oauth_config_test_error') . ': ' . $e->getMessage()); } redirect(admin_url('desk_moloni/oauth')); } /** * Get OAuth status via AJAX */ public function get_status() { // Verify AJAX request if (!$this->input->is_ajax_request()) { show_404(); } try { $status = [ 'oauth' => $this->moloni_oauth->get_status(), 'tokens' => $this->token_manager->get_token_status(), 'timestamp' => date('Y-m-d H:i:s') ]; $this->output ->set_content_type('application/json') ->set_output(json_encode($status)); } catch (Exception $e) { $this->output ->set_status_header(500) ->set_content_type('application/json') ->set_output(json_encode(['error' => $e->getMessage()])); } } /** * Export OAuth logs */ public function export_logs() { try { // Check permissions if (!has_permission('desk_moloni', '', 'view')) { throw new Exception('Insufficient permissions'); } // Load API log model $this->load->model('desk_moloni_api_log_model'); // Get logs from last 30 days $logs = $this->desk_moloni_api_log_model->get_logs([ 'start_date' => date('Y-m-d', strtotime('-30 days')), 'end_date' => date('Y-m-d'), 'limit' => 1000 ]); // Generate CSV $csv_data = $this->generate_logs_csv($logs); // Set headers for file download $filename = 'moloni_oauth_logs_' . date('Y-m-d') . '.csv'; $this->output ->set_content_type('application/csv') ->set_header('Content-Disposition: attachment; filename="' . $filename . '"') ->set_output($csv_data); } catch (Exception $e) { set_alert('danger', _l('export_logs_failed') . ': ' . $e->getMessage()); redirect(admin_url('desk_moloni/oauth')); } } /** * Handle OAuth configuration form submission */ private function handle_oauth_configuration() { try { // Validate CSRF token if (!$this->security->get_csrf_hash()) { throw new Exception('Invalid CSRF token'); } // Get form data $client_id = $this->input->post('client_id', true); $client_secret = $this->input->post('client_secret', true); $use_pkce = (bool)$this->input->post('use_pkce'); // Validate required fields if (empty($client_id) || empty($client_secret)) { throw new Exception('Client ID and Client Secret are required'); } // Configure OAuth $options = [ 'use_pkce' => $use_pkce, 'timeout' => (int)$this->input->post('timeout', true) ?: 30 ]; $success = $this->moloni_oauth->configure($client_id, $client_secret, $options); if ($success) { log_activity('Desk-Moloni: OAuth configuration updated by ' . get_staff_full_name()); set_alert('success', _l('oauth_configuration_saved')); } else { throw new Exception('Configuration save failed'); } } catch (Exception $e) { log_activity('Desk-Moloni: OAuth configuration failed - ' . $e->getMessage()); set_alert('danger', _l('oauth_configuration_failed') . ': ' . $e->getMessage()); } } /** * Test API connection after OAuth setup */ private function test_api_connection() { try { $this->load->library('desk_moloni/moloni_api_client'); // Try to get companies list (basic API test) $companies = $this->moloni_api_client->make_request('companies/getAll'); if (!empty($companies)) { log_activity('Desk-Moloni: API connection test successful'); set_alert('info', _l('api_connection_test_passed')); } } catch (Exception $e) { log_activity('Desk-Moloni: API connection test failed - ' . $e->getMessage()); set_alert('warning', _l('api_connection_test_failed') . ': ' . $e->getMessage()); } } /** * Generate CSV data from logs * * @param array $logs Log entries * @return string CSV data */ private function generate_logs_csv($logs) { $csv_lines = []; // Add header $csv_lines[] = 'Timestamp,Endpoint,Method,Status,Error,User'; // Add log entries foreach ($logs as $log) { $csv_lines[] = sprintf( '"%s","%s","%s","%s","%s","%s"', $log['timestamp'], $log['endpoint'] ?? '', $log['method'] ?? 'POST', $log['error'] ? 'ERROR' : 'SUCCESS', str_replace('"', '""', $log['error'] ?? ''), $log['user_name'] ?? 'System' ); } return implode("\n", $csv_lines); } /** * Security check for sensitive operations * * @param string $action Action being performed * @return bool Security check passed */ private function security_check($action) { // Rate limiting for sensitive operations $rate_limit_key = 'oauth_action_' . get_staff_user_id() . '_' . $action; $attempts = $this->session->userdata($rate_limit_key) ?? 0; if ($attempts >= 5) { throw new Exception('Too many attempts. Please wait before trying again.'); } $this->session->set_userdata($rate_limit_key, $attempts + 1); return true; } }