PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.0
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.0
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
← All changes | includes/Shortcodes/ShortcodeManager.php +63 -4 2.1.22.4.0 View file →
@@ -61,14 +61,49 @@
61 61
62 62 $url = get_permalink($invoice->getId());
63 63 // Only use secure link if enabled in settings and available
64 64 $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes';
65 - if ($secure_links_enabled && class_exists('\EasyInvoicePro\Controllers\PermalinkController')) {
66 - $secure_url = \EasyInvoicePro\Controllers\PermalinkController::getInvoiceSecureLinkUrl($invoice->getId());
65 + if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) {
66 + $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getInvoiceSecureLinkUrl($invoice->getId());
67 67 if ($secure_url) {
68 68 $url = $secure_url;
69 69 }
70 70 }
71 +
72 + // SECURITY: the per-invoice access token authorises manual-payment
73 + // submission for the named invoice. Two rules govern when it can
74 + // be attached to a shortcode-rendered URL:
75 + //
76 + // 1. NEVER mint a new token from this code path. The shortcode
77 + // is callable by anyone able to author rendered content
78 + // (Contributors previewing drafts, public template includes),
79 + // so auto-minting via invoiceAccessToken() here would let a
80 + // low-privileged actor create payment-auth tokens for any
81 + // invoice ID they can guess. We read existing tokens only.
82 + // 2. Even reading an existing token is gated. Only the admin
83 + // (manage_options) or a viewer who already passes the
84 + // ownership check (canSubmitPaymentForInvoice — which
85 + // itself accepts a token already presented via ?ik=) gets
86 + // the keyed URL. Everyone else gets the bare permalink.
87 + //
88 + // Legitimate flows still work:
89 + // * Admin sends invoice email -> EmailManager (server-trusted)
90 + // mints + embeds the token in the emailed URL directly.
91 + // * Client clicks the emailed link -> arrives with ?ik= in URL
92 + // -> canSubmitPaymentForInvoice passes -> shortcode renders
93 + // other invoice URLs on the page with the keyed form too.
94 + // * Admin embedding [easy_invoice_url] on an admin-context
95 + // page sees the keyed URL via the manage_options branch.
96 + $invoice_id_int = (int) $invoice->getId();
97 + $invoice_access_token = '';
98 + if (current_user_can('manage_options')
99 + || \EasyInvoice\Controllers\InvoiceController::canSubmitPaymentForInvoice($invoice_id_int, $invoice)) {
100 + $invoice_access_token = \EasyInvoice\Controllers\InvoiceController::invoiceAccessTokenIfExists($invoice_id_int);
101 + }
102 + if ($invoice_access_token !== '' && $url) {
103 + $url = add_query_arg('ik', $invoice_access_token, $url);
104 + }
105 +
71 106 $text = !empty($atts['text']) ? $atts['text'] : $invoice->getNumber();
72 107 $class = esc_attr($atts['class']);
73 108 $target = esc_attr($atts['target']);
74 109
@@ -109,14 +144,38 @@
109 144 }
110 145
111 146 $url = get_permalink($quote->getId());
112 147 $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes';
113 - if ($secure_links_enabled && class_exists('\EasyInvoicePro\Controllers\PermalinkController')) {
114 - $secure_url = \EasyInvoicePro\Controllers\PermalinkController::getQuoteSecureLinkUrl($quote->getId());
148 + if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) {
149 + $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getQuoteSecureLinkUrl($quote->getId());
115 150 if ($secure_url) {
116 151 $url = $secure_url;
117 152 }
118 153 }
154 +
155 + // SECURITY (CVE-2026-9021 + follow-up): the per-quote access
156 + // token authorises Accept/Decline. Same two-rule policy as the
157 + // invoice shortcode above (see renderInvoiceUrl for full
158 + // rationale):
159 + //
160 + // 1. NEVER mint a new token from the shortcode path — readers
161 + // only. Trusted email composition (EmailManager) keeps
162 + // using quoteAccessToken() which mints on first send.
163 + // 2. Only disclose an existing token to an admin or a viewer
164 + // who already passes the ownership check (canActOnQuote).
165 + // Other viewers get the bare permalink — they can view
166 + // the quote but not Accept/Decline until they arrive via
167 + // a legitimately-emailed link.
168 + $quote_id_int = (int) $quote->getId();
169 + $quote_access_token = '';
170 + if (current_user_can('manage_options')
171 + || \EasyInvoice\Controllers\QuoteController::canActOnQuote($quote_id_int, $quote)) {
172 + $quote_access_token = \EasyInvoice\Controllers\QuoteController::quoteAccessTokenIfExists($quote_id_int);
173 + }
174 + if ($quote_access_token !== '' && $url) {
175 + $url = add_query_arg('qk', $quote_access_token, $url);
176 + }
177 +
119 178 $text = !empty($atts['text']) ? $atts['text'] : $quote->getNumber();
120 179 $class = esc_attr($atts['class']);
121 180 $target = esc_attr($atts['target']);
122 181