PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.12
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.12
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Services / Integrations / GlobalIntegrationService.php

GlobalIntegrationService.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.12, at app/Services/Integrations/GlobalIntegrationService.php

140 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\App\Services\Integrations;
4
5 use Exception;
6 use FluentForm\Framework\Support\Arr;
7 class GlobalIntegrationService
8 {
9 // Sentinel written over connected credential values on read (FINDING-16) and
10 // detected again on save so a re-posted mask never overwrites a live secret.
11 const REDACTION_MASK = '********';
12
13 public function get($attr)
14 {
15 $settingsKey = sanitize_text_field(Arr::get($attr, 'settings_key'));
16 $settings = apply_filters_deprecated(
17 'fluentform_global_integration_settings_' . $settingsKey,
18 [
19 []
20 ],
21 FLUENTFORM_FRAMEWORK_UPGRADE,
22 'fluentform/global_integration_settings_' . $settingsKey,
23 'Use fluentform/global_integration_settings_' . $settingsKey . ' instead of fluentform_global_integration_settings_' . $settingsKey
24 );
25 $settings = apply_filters('fluentform/global_integration_settings_' . $settingsKey, $settings);
26 $fieldSettings = apply_filters_deprecated(
27 'fluentform_global_integration_fields_' . $settingsKey,
28 [
29 []
30 ],
31 FLUENTFORM_FRAMEWORK_UPGRADE,
32 'fluentform/global_integration_fields_' . $settingsKey,
33 'Use fluentform/global_integration_fields_' . $settingsKey . ' instead of fluentform_global_integration_fields_' . $settingsKey
34 );
35 $fieldSettings = apply_filters('fluentform/global_integration_fields_' . $settingsKey, $fieldSettings);
36 if (!$fieldSettings) {
37 $message = __('Sorry! No integration failed found with: ', 'fluentform').$settingsKey;
38 return [
39 'status' => false,
40 'message'=> $message,
41 'integration' => $settings,
42 'settings' => $fieldSettings,
43 ];
44 }
45
46 if (!Arr::exists($fieldSettings,'save_button_text')) {
47 $fieldSettings['save_button_text'] = __('Save Settings', 'fluentform');
48 }
49
50 if (!Arr::exists($fieldSettings,'valid_message')) {
51 $fieldSettings['valid_message'] = __('Your API Key is valid', 'fluentform');
52 }
53
54 if (!Arr::exists($fieldSettings,'invalid_message')) {
55 $fieldSettings['invalid_message'] = __('Your API Key is not valid', 'fluentform');
56 }
57
58 // SECURITY (FINDING-16): the stored settings carry long-lived third-party credentials
59 // (API keys, tokens). This read endpoint is reachable by delegated roles and the payload
60 // returned them verbatim — hide_on_valid only hid the field in the Vue UI, not in the REST
61 // response. When the integration is connected and its credential fields are hidden anyway
62 // (hide_on_valid), redact the declared field values so the browser never receives the
63 // secret. The save path restores any field posted back still masked (see
64 // unmaskCredentials), so a re-saved connected integration — e.g. the "Verify Connection
65 // Again" button, which re-POSTs the loaded payload — can never overwrite a live credential
66 // with the mask.
67 if (is_array($settings) && !empty($fieldSettings['hide_on_valid']) && !empty($settings['status'])) {
68 foreach (array_keys((array) Arr::get($fieldSettings, 'fields', [])) as $credentialKey) {
69 if (!empty($settings[$credentialKey]) && is_string($settings[$credentialKey])) {
70 $settings[$credentialKey] = self::REDACTION_MASK;
71 }
72 }
73 }
74
75 return [
76 'status' => true,
77 'integration' => $settings,
78 'settings' => $fieldSettings,
79 ];
80 }
81
82 /**
83 * Restore any credential the browser posted back still masked (REDACTION_MASK)
84 * to its real stored value before it is persisted. get() redacts connected
85 * credentials on read; without this, re-saving a connected integration — the
86 * "Verify Connection Again" button re-POSTs the loaded payload verbatim, and
87 * several handlers persist BEFORE their auth test — would overwrite a live key
88 * with '********'. Stored settings come from the same filter get() reads.
89 */
90 public function unmaskCredentials($settingsKey, $integration)
91 {
92 if (!is_array($integration) || !in_array(self::REDACTION_MASK, $integration, true)) {
93 return $integration;
94 }
95
96 $stored = apply_filters('fluentform/global_integration_settings_' . $settingsKey, []);
97 if (!is_array($stored)) {
98 return $integration;
99 }
100
101 foreach ($integration as $key => $value) {
102 if (self::REDACTION_MASK === $value && isset($stored[$key]) && is_string($stored[$key])) {
103 $integration[$key] = $stored[$key];
104 }
105 }
106
107 return $integration;
108 }
109
110 public function isEnabled($integrationKey)
111 {
112 $globalModules = get_option('fluentform_global_modules_status');
113 $isEnabled = $globalModules && isset($globalModules[$integrationKey]) && 'yes' == $globalModules[$integrationKey];
114
115 return apply_filters('fluentform/is_integration_enabled_'.$integrationKey, $isEnabled);
116 }
117
118 /**
119 * @param $args - key value pair array
120 * @throws Exception
121 * @return void
122 */
123 public function updateModuleStatus($args) {
124 $moduleKey = sanitize_text_field(Arr::get($args, 'module_key'));
125 $moduleStatus = sanitize_text_field(Arr::get($args, 'module_status'));
126 if (!$moduleKey || !in_array($moduleStatus, ['yes', 'no'])) {
127 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
128 throw new Exception(__('Status update failed. Not valid module or status', 'fluentform'));
129 }
130 try {
131 $modules = (array)get_option('fluentform_global_modules_status');
132 $modules[$moduleKey] = $moduleStatus;
133 update_option('fluentform_global_modules_status', $modules, 'no');
134 } catch (Exception $e) {
135 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
136 throw new Exception($e->getMessage());
137 }
138 }
139 }
140