(dev/core#2258) SMTP Password - If CRED_KEYS is defined during upgrade, use it
[civicrm-core.git] / CRM / Upgrade / Incremental / php / FiveThirtyFour.php
CommitLineData
e10c0223
C
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12/**
13 * Upgrade logic for FiveThirtyFour */
14class CRM_Upgrade_Incremental_php_FiveThirtyFour extends CRM_Upgrade_Incremental_Base {
15
16 /**
17 * Compute any messages which should be displayed beforeupgrade.
18 *
19 * Note: This function is called iteratively for each upcoming
20 * revision to the database.
21 *
22 * @param string $preUpgradeMessage
23 * @param string $rev
24 * a version number, e.g. '4.4.alpha1', '4.4.beta3', '4.4.0'.
25 * @param null $currentVer
26 */
27 public function setPreUpgradeMessage(&$preUpgradeMessage, $rev, $currentVer = NULL) {
3d839125 28 if ($rev === '5.34.alpha1') {
29 $xoauth2Value = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_MailSettings', 'protocol', 'IMAP_XOAUTH2');
30 if (!empty($xoauth2Value)) {
31 if ($this->isXOAUTH2InUse($xoauth2Value)) {
45d97834 32 $preUpgradeMessage .= '<p>' . $this->getXOAuth2Warning() . '</p>';
3d839125 33 }
34 }
35 }
8daf06d6
TO
36
37 if ($rev === '5.34.alpha1') {
38 if (extension_loaded('mcrypt') && !empty(self::findSmtpPasswords())) {
39 // NOTE: We don't re-encrypt automatically because the old "civicrm.settings.php" lacks a good key, and we don't keep the old encryption because the format is ambiguous.
40 // The admin may forget to re-enable. That's OK -- this only affects 1 field, this is a secondary defense, and (in the future) we can remind the admin via status-checks.
aa7a6575
TO
41 $preUpgradeMessage .= '<p>' . ts('This system has an encrypted SMTP password. Encryption in v5.34+ requires CIVICRM_CRED_KEYS. You may <a href="%1" target="_blank">setup CIVICRM_CRED_KEYS</a> before or after upgrading. If you choose to wait, then the SMTP password will be stored as plain-text until you setup CIVICRM_CRED_KEYS.', [
42 1 => 'https://docs.civicrm.org/sysadmin/en/latest/upgrade/version-specific/#smtp-password',
43 ]) . '</p>';
8daf06d6
TO
44 }
45 foreach ($GLOBALS['civicrm_setting'] ?? [] as $entity => $overrides) {
46 if (extension_loaded('mcrypt') && !empty($overrides['mailing_backend']['smtpPassword']) && $overrides['mailing_backend']['outBound_option'] == 0) {
47 // This is a fairly unlikely situation. I'm sure it's *useful* to set smtpPassword via $civicrm_setting (eg for dev or multitenant).
48 // But historically it had to follow the rules of CRM_Utils_Crypt:
49 // - For non-mcrypt servers, that was easy/plaintext. That'll work just as well going forward. We don't show any warnings about that.
50 // - For mcrypt servers, the value had to be encrypted. It's not easy to pick the right value for that. Maybe someone with multitenant would have had
51 // enough incentive to figure this out... but they'd probably get stymied by the fact that each tenant has a different SITE_KEY.
52 // All of which is to say: if someone has gotten into a valid+working scenario of overriding smtpPassword on an mcrypt-enabled system, then they're
53 // savvy enough to figure out the migration details. We just need to point them at the problem.
54 $settingPath = sprintf('$civicrm_setting[%s][%s][%s]', var_export($entity, 1), var_export('mailing_backend', 1), var_export('smtpPassword', 1));
55 $prose = ts('This system has a PHP override for the SMTP password (%1). The override was most likely encrypted with an old mechanism. After upgrading, you must verify and/or revise this setting.', [
56 1 => $settingPath,
57 ]);
58 $preUpgradeMessage = '<p>' . $prose . '</p>';
59 }
60 }
61 }
e10c0223
C
62 }
63
64 /**
65 * Compute any messages which should be displayed after upgrade.
66 *
67 * @param string $postUpgradeMessage
68 * alterable.
69 * @param string $rev
70 * an intermediate version; note that setPostUpgradeMessage is called repeatedly with different $revs.
71 */
72 public function setPostUpgradeMessage(&$postUpgradeMessage, $rev) {
3d839125 73 if ($rev === '5.34.alpha1') {
74 $xoauth2Value = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_MailSettings', 'protocol', 'IMAP_XOAUTH2');
75 if (!empty($xoauth2Value)) {
76 if ($this->isXOAUTH2InUse($xoauth2Value)) {
45d97834 77 $postUpgradeMessage .= '<div class="crm-error"><ul><li>' . $this->getXOAuth2Warning() . '</li></ul></div>';
3d839125 78 }
79 }
80 }
e10c0223
C
81 }
82
83 /*
84 * Important! All upgrade functions MUST add a 'runSql' task.
85 * Uncomment and use the following template for a new upgrade version
86 * (change the x in the function name):
87 */
88
6a62a64e 89 /**
90 * Upgrade function.
91 *
92 * @param string $rev
93 */
94 public function upgrade_5_34_alpha1(string $rev): void {
95 $this->addTask(ts('Upgrade DB to %1: SQL', [1 => $rev]), 'runSql', $rev);
8daf06d6 96
aa7a6575 97 if (!empty(self::findSmtpPasswords())) {
8daf06d6
TO
98 $this->addTask('Migrate SMTP password', 'migrateSmtpPasswords');
99 }
100
6a62a64e 101 $this->addTask('core-issue#365 - Add created_date to civicrm_action_schedule', 'addColumn',
102 'civicrm_action_schedule', 'created_date', "timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'When was the schedule reminder created.'");
103
104 $this->addTask('core-issue#365 - Add modified_date to civicrm_action_schedule', 'addColumn',
105 'civicrm_action_schedule', 'modified_date', "timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'When was the schedule reminder created.'");
106
107 $this->addTask('core-issue#365 - Add effective_start_date to civicrm_action_schedule', 'addColumn',
108 'civicrm_action_schedule', 'effective_start_date', "timestamp NULL COMMENT 'Earliest date to consider start events from.'");
109
110 $this->addTask('core-issue#365 - Add effective_end_date to civicrm_action_schedule', 'addColumn',
111 'civicrm_action_schedule', 'effective_end_date', "timestamp NULL COMMENT 'Latest date to consider end events from.'");
c6d30d7f 112
113 $this->addTask('Set defaults and required on financial type boolean fields', 'updateFinancialTypeTable');
114 $this->addTask('Set defaults and required on pledge fields', 'updatePledgeTable');
3d839125 115
116 $this->addTask('Remove never used IMAP_XOAUTH2 option value', 'removeUnusedXOAUTH2');
6a62a64e 117 }
e10c0223 118
8daf06d6
TO
119 /**
120 * @return array
121 * A list of "civicrm_setting" records which have
122 * SMTP passwords, or NULL.
123 */
124 protected static function findSmtpPasswords() {
125 $query = CRM_Utils_SQL_Select::from('civicrm_setting')
126 ->where('name = "mailing_backend"');
127
128 $matches = [];
129 foreach ($query->execute()->fetchAll() as $setting) {
130 $value = unserialize($setting['value']);
131 if (!empty($value['smtpPassword'])) {
132 $matches[] = $setting;
133 }
134 }
135
136 return $matches;
137 }
138
139 /**
140 * Find any SMTP passwords. Remove the CRM_Utils_Crypt encryption.
141 *
142 * Note: This task is only enqueued if mcrypt is active.
143 *
144 * @param \CRM_Queue_TaskContext $ctx
145 *
146 * @return bool
147 */
148 public static function migrateSmtpPasswords(CRM_Queue_TaskContext $ctx) {
149 $settings = self::findSmtpPasswords();
aa7a6575
TO
150 $cryptoToken = new \Civi\Crypto\CryptoToken(\Civi\Crypto\CryptoRegistry::createDefaultRegistry());
151
8daf06d6
TO
152 foreach ($settings as $setting) {
153 $value = unserialize($setting['value']);
aa7a6575
TO
154 $plain = CRM_Utils_Crypt::decrypt($value['smtpPassword']);
155 $value['smtpPassword'] = $cryptoToken->encrypt($plain, 'CRED');
8daf06d6
TO
156 CRM_Core_DAO::executeQuery('UPDATE civicrm_setting SET value = %2 WHERE id = %1', [
157 1 => [$setting['id'], 'Positive'],
158 2 => [serialize($value), 'String'],
159 ]);
160 }
161 return TRUE;
162 }
163
c6d30d7f 164 /**
165 * Update financial type table to reflect recent schema changes.
166 *
167 * @param \CRM_Queue_TaskContext $ctx
168 *
169 * @return bool
170 */
171 public static function updateFinancialTypeTable(CRM_Queue_TaskContext $ctx): bool {
172 // Make sure there are no existing NULL values in the fields we are about to make required.
173 CRM_Core_DAO::executeQuery('
174 UPDATE civicrm_financial_type
175 SET is_active = COALESCE(is_active, 0),
176 is_reserved = COALESCE(is_reserved, 0),
177 is_deductible = COALESCE(is_deductible, 0)
178 WHERE is_reserved IS NULL OR is_active IS NULL OR is_deductible IS NULL
179 ');
180 CRM_Core_DAO::executeQuery("
181 ALTER TABLE civicrm_financial_type
182 MODIFY COLUMN `is_deductible` tinyint(4) DEFAULT 0 NOT NULL COMMENT 'Is this financial type tax-deductible? If true, contributions of this type may be fully OR partially deductible - non-deductible amount is stored in the Contribution record.',
183 MODIFY COLUMN `is_reserved` tinyint(4) DEFAULT 0 NOT NULL COMMENT 'Is this a predefined system object?',
184 MODIFY COLUMN `is_active` tinyint(4) DEFAULT 1 NOT NULL COMMENT 'Is this property active?'
185 ");
186
187 return TRUE;
188 }
189
190 /**
191 * Update pledge table to reflect recent schema changes making fields required.
192 *
193 * @param \CRM_Queue_TaskContext $ctx
194 *
195 * @return bool
196 */
197 public static function updatePledgeTable(CRM_Queue_TaskContext $ctx): bool {
198 // Make sure there are no existing NULL values in the fields we are about to make required.
199 CRM_Core_DAO::executeQuery('
200 UPDATE civicrm_pledge
201 SET is_test = COALESCE(is_test, 0),
202 frequency_unit = COALESCE(frequency_unit, "month"),
203 # Cannot imagine this would be null but if it were...
204 installments = COALESCE(installments, 0),
205 # this does not seem plausible either.
206 status_id = COALESCE(status_id, 1)
207 WHERE is_test IS NULL OR frequency_unit IS NULL OR installments IS NULL OR status_id IS NULL
208 ');
209 CRM_Core_DAO::executeQuery("
210 ALTER TABLE civicrm_pledge
211 MODIFY COLUMN `frequency_unit` varchar(8) DEFAULT 'month' NOT NULL COMMENT 'Time units for recurrence of pledge payments.',
212 MODIFY COLUMN `installments` int(10) unsigned DEFAULT 1 NOT NULL COMMENT 'Total number of payments to be made.',
213 MODIFY COLUMN `status_id` int(10) unsigned NOT NULL COMMENT 'Implicit foreign key to civicrm_option_values in the pledge_status option group.',
214 MODIFY COLUMN `is_test` tinyint(4) DEFAULT 0 NOT NULL
215 ");
216 return TRUE;
217 }
e10c0223 218
3d839125 219 /**
220 * This option value was never used, but check anyway if someone happens
221 * to be using it and then ask them to report what they're doing with it.
222 * There's no way to send a message to the user during the task, so we have
223 * to check it here and also as a pre/post upgrade message.
224 * Similar to removeGooglePlusOption from 5.23 except there we know some
225 * people would have used it.
226 */
227 public static function removeUnusedXOAUTH2(CRM_Queue_TaskContext $ctx) {
228 $xoauth2Value = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_MailSettings', 'protocol', 'IMAP_XOAUTH2');
229 if (!empty($xoauth2Value)) {
230 if (!self::isXOAUTH2InUse($xoauth2Value)) {
231 CRM_Core_DAO::executeQuery("DELETE ov FROM civicrm_option_value ov
232INNER JOIN civicrm_option_group og
233ON (og.name = 'mail_protocol' AND ov.option_group_id = og.id)
234WHERE ov.value = %1",
235 [1 => [$xoauth2Value, 'Positive']]);
236 }
237 }
238 return TRUE;
239 }
240
241 /**
242 * Determine if option value is enabled or used in mail settings.
243 * @return bool
244 */
245 private static function isXOAUTH2InUse($xoauth2Value) {
246 $enabled = (bool) CRM_Core_DAO::SingleValueQuery("SELECT ov.is_active FROM civicrm_option_value ov
247INNER JOIN civicrm_option_group og
248ON (og.name = 'mail_protocol' AND ov.option_group_id = og.id)
249WHERE ov.value = %1",
250 [1 => [$xoauth2Value, 'Positive']]);
251 $usedInMailSettings = (bool) CRM_Core_DAO::SingleValueQuery("SELECT id FROM civicrm_mail_settings WHERE protocol = %1", [1 => [$xoauth2Value, 'Positive']]);
252 return $enabled || $usedInMailSettings;
253 }
254
45d97834
TO
255 /**
256 * @return string
257 */
258 private function getXOAuth2Warning():string {
259 // Leaving out ts() since it's unlikely this message will ever
260 // be displayed to anyone.
261 return strtr(
262 'This system has enabled "IMAP_XOAUTH2" which was experimentally declared in CiviCRM v5.24. CiviCRM v5.33+ includes a supported replacement ("oauth-client"), and the experimental "IMAP_XOAUTH2" should be removed. Please visit %1 to discuss.',
263 [
264 '%1' => '<a target="_blank" href="https://lab.civicrm.org/dev/core/-/issues/2264">dev/core#2264</a>',
265 ]
266 );
267 }
268
e10c0223 269}