Changeset 3781981
- Timestamp:
- 11/03/2026 02:11:25 PM (63 minutes ago)
- Location:
- simpliroot-lead-capture/trunk
- Files:
-
- 2 edited
-
readme.txt (modified) (4 diffs)
-
simpliroot-lead-capture.php (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
simpliroot-lead-capture/trunk/readme.txt
r3771277 r3781981 5 5 Tested up to: 7.0 6 6 Requires PHP: 7.4 7 Stable tag: 3. 9.07 Stable tag: 3.10.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 53 53 Paste your project token and pick what to track. "Test connection" and "Send test lead" buttons confirm everything works. 54 54 55 = Optional: one-click support access = 56 57 If you work with the SimpliRoot team, you can let an authorized SimpliRoot panel user open your WordPress admin from the panel with one click — without you ever sharing a password. This is **off by default** and must be switched on by a WordPress administrator on the SimpliRoot settings screen (under "Remote support access"). When on, the plugin creates a dedicated `simpliroot-support` administrator account and signs each login request with your project token, which never travels over the network. Every entry is logged on the settings screen and in your panel. Turn the toggle off, or delete the `simpliroot-support` user, to revoke access immediately. 58 55 59 = Requirements = 56 60 … … 77 81 78 82 7. **When you click "Send test lead"** (admin only): a sample lead is sent to the same lead endpoint so you can verify the connection. 83 84 8. **Regular check-in**: the plugin periodically calls `https://app.simpliroot.com/api/dni/ping` with your project id, site domain and plugin version to fetch the project's current settings. If you enable "Remote support access" (one-click support login, off by default — see above), this check-in also reports that the option is on, so the panel can offer the login. No login happens unless an authorized panel user initiates it and the request is cryptographically verified by the plugin. 79 85 80 86 Your use of the SimpliRoot service is governed by its terms and privacy policy: … … 122 128 123 129 130 131 = 3.10.0 = 132 * New optional "Remote support access": let an authorized SimpliRoot panel user open your wp-admin with one click, without sharing a password. Off by default; a WordPress administrator opts in on the settings screen. Each login is signed with your project token (never sent over the network), uses a dedicated `simpliroot-support` administrator account, is one-time and expires in seconds, and is logged on both sides. Revoke any time by turning the toggle off or deleting the support user. 124 133 125 134 = 3.9.0 = -
simpliroot-lead-capture/trunk/simpliroot-lead-capture.php
r3771277 r3781981 4 4 * Plugin URI: https://app.simpliroot.com 5 5 * Description: Live chat with an AI bot, plus lead capture (Contact Form 7, Elementor, WPForms, Gravity, Fluent + any other form) and call tracking — all sent to your SimpliRoot CRM server-side (no cookies/GTM needed), with full ad-platform attribution. Configure with just your project token. 6 * Version: 3. 9.06 * Version: 3.10.0 7 7 * Requires at least: 5.7 8 8 * Requires PHP: 7.4 … … 16 16 if (!defined('ABSPATH')) exit; 17 17 18 define('SIMPLIROOT_VERSION', '3. 9.0');18 define('SIMPLIROOT_VERSION', '3.10.0'); 19 19 define('SIMPLIROOT_API_BASE', defined('SIMPLIROOT_API_BASE') ? SIMPLIROOT_API_BASE : 'https://app.simpliroot.com'); 20 20 define('SIMPLIROOT_OPT', 'simpliroot_settings'); // option key … … 55 55 'domain' => '', 56 56 'last_check' => 0, 57 // Remote one-click admin login (SSO) from the SimpliRoot panel. OFF by 58 // default: the site admin must opt in here, which is the client's consent. 59 // Every gate fails CLOSED — see the /sso endpoint at the bottom. 60 'enable_sso' => 0, 57 61 ]); 58 62 } … … 111 115 'domain' => wp_parse_url(home_url(), PHP_URL_HOST), 112 116 'v' => SIMPLIROOT_VERSION, 117 // Report local SSO consent so the panel shows the "Enter WordPress" 118 // button only when this site has actually opted in. 119 'sso' => !empty($s['enable_sso']) ? 1 : 0, 113 120 ], 114 121 ]); … … 1229 1236 $s = simpliroot_local_settings(); 1230 1237 $s['token'] = sanitize_text_field(wp_unslash($_POST['token'] ?? '')); 1238 // Local consent toggle: the WP admin opts in to one-click remote admin login. 1239 // Unchecked box sends nothing, so an absent key means OFF. 1240 $s['enable_sso'] = empty($_POST['enable_sso']) ? 0 : 1; 1231 1241 // enable_forms/enable_calls are NOT edited here anymore — the SimpliRoot 1232 1242 // panel controls them (see simpliroot_server_config). Local values stay at … … 1549 1559 </div> 1550 1560 1561 <div class="card" style="max-width:760px;padding:4px 16px 16px"> 1562 <h2>Remote support access</h2> 1563 <table class="form-table"> 1564 <tr> 1565 <th scope="row">One-click admin login</th> 1566 <td> 1567 <label> 1568 <input type="checkbox" name="enable_sso" value="1" <?php checked(!empty($s['enable_sso'])); ?> <?php disabled(!$connected); ?>> 1569 Let the SimpliRoot team open this site's admin from the SimpliRoot panel, with one click, without you sharing a password. 1570 </label> 1571 <p class="description"> 1572 <strong>Off by default.</strong> When on, an authorized SimpliRoot panel user can be logged in to <code>wp-admin</code> as a dedicated 1573 <code>simpliroot-support</code> administrator account that this plugin creates. Each entry is cryptographically signed with your project 1574 token (the same secret already used by this plugin — it never travels over the network) and is logged here and in the panel. 1575 Turn this off, or delete the <code>simpliroot-support</code> user, to revoke access immediately. 1576 <?php if (!$connected): ?><br><em>Connect a project token first to enable this.</em><?php endif; ?> 1577 </p> 1578 <?php 1579 $log = get_option('simpliroot_sso_log', []); 1580 if (is_array($log) && $log) { 1581 echo '<p class="description" style="margin-top:10px"><strong>Recent logins:</strong></p><ul style="margin:0 0 0 4px;font-size:12px;color:#555">'; 1582 foreach (array_slice(array_reverse($log), 0, 5) as $e) { 1583 echo '<li>' . esc_html(gmdate('Y-m-d H:i', (int) ($e['t'] ?? 0))) . ' UTC — ' 1584 . esc_html($e['email'] ?? '?') . ' — ' . esc_html($e['ip'] ?? '?') 1585 . ' — ' . esc_html($e['result'] ?? '?') . '</li>'; 1586 } 1587 echo '</ul>'; 1588 } 1589 ?> 1590 </td> 1591 </tr> 1592 </table> 1593 </div> 1594 1551 1595 </form> 1552 1596 </div> … … 1620 1664 ], 200); 1621 1665 } 1666 1667 1668 // ═════════════════════════════════════════════════════════════════════════════ 1669 // SSO — one-click admin login from the SimpliRoot panel ("Enter WordPress") 1670 // ═════════════════════════════════════════════════════════════════════════════ 1671 // 1672 // An authorized user in the SimpliRoot panel clicks "Enter WordPress" for a 1673 // project. The panel mints a SHORT-LIVED, SINGLE-USE ticket signed with the 1674 // project token (the same shared secret used by /invalidate — it never travels 1675 // over the network, only the signature computed from it) and the browser POSTs 1676 // it here. We verify, log the panel user in as a DEDICATED support account and 1677 // redirect to wp-admin. No password ever changes hands. 1678 // 1679 // Wire contract (panel → browser form POST → here): 1680 // 1681 // POST https://{domena_klienta}/wp-json/simpliroot/v1/sso (form-encoded) 1682 // p = base64url( json { 1683 // "pu_id": <int> panel user id 1684 // "pu_email": <string> panel user email (for the audit log) 1685 // "project_id":<int> must match this site's project 1686 // "ts": <int> unix seconds 1687 // "nonce": <string> random, one-time 1688 // } ) 1689 // s = hex( hmac_sha256( p, project_token ) ) 1690 // 1691 // POST (not a GET link) on purpose: the ticket rides in the request body, so it 1692 // never lands in access logs, the Referer header or browser history. 1693 // 1694 // EVERY gate fails CLOSED. SSO off, no token, wrong project, bad signature, a 1695 // ticket older than 90 s, or a reused nonce → nothing happens but a logged 401/ 1696 // 403. Rotating the project token (same path as /invalidate) kills old tickets. 1697 // The client revokes access any time by unticking the toggle or deleting the 1698 // simpliroot-support user. 1699 1700 add_action('rest_api_init', function () { 1701 register_rest_route('simpliroot/v1', '/sso', [ 1702 'methods' => 'POST', 1703 'permission_callback' => '__return_true', // own HMAC verification below 1704 'callback' => 'simpliroot_rest_sso', 1705 ]); 1706 }); 1707 1708 // base64url → raw. Tolerates missing padding. 1709 function simpliroot_b64url_decode($in) { 1710 $in = strtr((string) $in, '-_', '+/'); 1711 $pad = strlen($in) % 4; 1712 if ($pad) $in .= str_repeat('=', 4 - $pad); 1713 return base64_decode($in, true); 1714 } 1715 1716 function simpliroot_client_ip() { 1717 $ip = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : ''; 1718 return $ip !== '' ? $ip : '?'; 1719 } 1720 1721 // Append an audit entry (kept trimmed) and mirror it to the PHP error log. 1722 function simpliroot_sso_log($email, $result) { 1723 $log = get_option('simpliroot_sso_log', []); 1724 if (!is_array($log)) $log = []; 1725 $log[] = [ 1726 't' => time(), 1727 'email' => (string) $email, 1728 'ip' => simpliroot_client_ip(), 1729 'result' => (string) $result, 1730 ]; 1731 if (count($log) > 50) $log = array_slice($log, -50); 1732 update_option('simpliroot_sso_log', $log, false); 1733 error_log(sprintf('[simpliroot] SSO %s for %s from %s', $result, $email ?: '?', simpliroot_client_ip())); 1734 } 1735 1736 // Get (or lazily create) the dedicated support administrator. Created ONLY while 1737 // SSO is on, so the account never exists without the client's consent. 1738 function simpliroot_sso_support_user() { 1739 $login = 'simpliroot-support'; 1740 $user = get_user_by('login', $login); 1741 if ($user) return $user; 1742 1743 $host = wp_parse_url(home_url(), PHP_URL_HOST) ?: 'site'; 1744 $email = 'support+' . preg_replace('/[^a-z0-9.-]/i', '', $host) . '@simpliroot.com'; 1745 if (email_exists($email)) $email = 'support+' . wp_generate_password(10, false) . '@simpliroot.com'; 1746 1747 $uid = wp_insert_user([ 1748 'user_login' => $login, 1749 'user_pass' => wp_generate_password(40, true, true), 1750 'user_email' => $email, 1751 'display_name' => 'SimpliRoot Support', 1752 'role' => 'administrator', 1753 ]); 1754 if (is_wp_error($uid)) return null; 1755 update_user_meta($uid, 'simpliroot_managed', 1); 1756 return get_user_by('id', $uid); 1757 } 1758 1759 function simpliroot_rest_sso(WP_REST_Request $req) { 1760 $s = simpliroot_local_settings(); 1761 1762 // Gate 1: consent + connection. Fail closed. 1763 if (empty($s['enable_sso']) || empty($s['project_id']) || empty($s['token'])) { 1764 simpliroot_sso_log('', 'blocked_disabled'); 1765 return new WP_REST_Response(['ok' => false, 'reason' => 'sso_disabled'], 403); 1766 } 1767 1768 $p = (string) $req->get_param('p'); 1769 $sig = (string) $req->get_param('s'); 1770 if ($p === '' || $sig === '') { 1771 simpliroot_sso_log('', 'bad_request'); 1772 return new WP_REST_Response(['ok' => false, 'reason' => 'missing_ticket'], 400); 1773 } 1774 1775 // Gate 2: signature over the exact payload string. 1776 $expect = hash_hmac('sha256', $p, (string) $s['token']); 1777 if (!hash_equals($expect, $sig)) { 1778 simpliroot_sso_log('', 'bad_signature'); 1779 return new WP_REST_Response(['ok' => false, 'reason' => 'bad_signature'], 401); 1780 } 1781 1782 $raw = simpliroot_b64url_decode($p); 1783 $data = $raw ? json_decode($raw, true) : null; 1784 if (!is_array($data) || !isset($data['project_id'], $data['ts'], $data['nonce'])) { 1785 simpliroot_sso_log('', 'bad_payload'); 1786 return new WP_REST_Response(['ok' => false, 'reason' => 'bad_payload'], 400); 1787 } 1788 1789 $email = isset($data['pu_email']) ? sanitize_email((string) $data['pu_email']) : ''; 1790 1791 // Gate 3: ticket is for THIS project. 1792 if ((int) $data['project_id'] !== (int) $s['project_id']) { 1793 simpliroot_sso_log($email, 'project_mismatch'); 1794 return new WP_REST_Response(['ok' => false, 'reason' => 'project_mismatch'], 403); 1795 } 1796 1797 // Gate 4: freshness (±90 s). 1798 if (abs(time() - (int) $data['ts']) > 90) { 1799 simpliroot_sso_log($email, 'stale'); 1800 return new WP_REST_Response(['ok' => false, 'reason' => 'stale'], 401); 1801 } 1802 1803 // Gate 5: single use. A reused nonce is a replay — refuse. 1804 $nkey = 'simpliroot_sso_n_' . md5((string) $data['nonce']); 1805 if (get_transient($nkey)) { 1806 simpliroot_sso_log($email, 'replay'); 1807 return new WP_REST_Response(['ok' => false, 'reason' => 'replay'], 401); 1808 } 1809 set_transient($nkey, 1, 180); // outlive the 90 s window comfortably 1810 1811 $user = simpliroot_sso_support_user(); 1812 if (!$user) { 1813 simpliroot_sso_log($email, 'user_error'); 1814 return new WP_REST_Response(['ok' => false, 'reason' => 'user_error'], 500); 1815 } 1816 1817 // Log the panel user in as the support account and send them to wp-admin. 1818 wp_set_auth_cookie($user->ID, false, is_ssl()); 1819 wp_set_current_user($user->ID); 1820 simpliroot_sso_log($email, 'ok'); 1821 1822 nocache_headers(); 1823 wp_safe_redirect(admin_url()); 1824 exit; 1825 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)