PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.3
2.4.3 2.4.4 2.4.2 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 All 60 releases
← All changes | includes/Shortcodes/ShortcodeManager.php +74 -0 2.1.0 → 2.4.3 View file →
@@ -59,8 +59,51 @@
59 59 return '<span class="easy-invoice-error">' . __('Invoice not found', 'easy-invoice') . '</span>';
60 60 }
61 61
62 62 $url = get_permalink($invoice->getId());
63 + // Only use secure link if enabled in settings and available
64 + $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes';
65 + if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) {
66 + $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getInvoiceSecureLinkUrl($invoice->getId());
67 + if ($secure_url) {
68 + $url = $secure_url;
69 + }
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 +
63 106 $text = !empty($atts['text']) ? $atts['text'] : $invoice->getNumber();
64 107 $class = esc_attr($atts['class']);
65 108 $target = esc_attr($atts['target']);
66 109
@@ -100,8 +143,39 @@
100 143 return '<span class="easy-invoice-error">' . __('Quote not found', 'easy-invoice') . '</span>';
101 144 }
102 145
103 146 $url = get_permalink($quote->getId());
147 + $secure_links_enabled = get_option('easy_invoice_pro_enable_secure_links', 'no') === 'yes';
148 + if ($secure_links_enabled && class_exists('\EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController')) {
149 + $secure_url = \EasyInvoicePro\Addons\SecureLinks\Controllers\PermalinkController::getQuoteSecureLinkUrl($quote->getId());
150 + if ($secure_url) {
151 + $url = $secure_url;
152 + }
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 +
104 178 $text = !empty($atts['text']) ? $atts['text'] : $quote->getNumber();
105 179 $class = esc_attr($atts['class']);
106 180 $target = esc_attr($atts['target']);
107 181