├── login-warning.php ├── readme.txt └── wp-bouncer.php /login-warning.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Login Warning 6 | 7 | 13 | 14 | 15 | 16 |
17 |

Login Warning

18 |

There was an issue with your log in. Your user account has logged in recently from a different location.

19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === WP Bouncer === 2 | Contributors: norcross 3 | Website Link: http://andrewnorcross.com/plugins/wp-bouncer/ 4 | Donate link: https://andrewnorcross.com/donate 5 | Tags: login, security, member, members, membership, memberships, susbcription, subscriptions 6 | Requires at least: 3.0 7 | Tested up to: 3.5 8 | Stable tag: 1.0.1 9 | License: GPLv2 or later 10 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 11 | 12 | Only allow one device to be logged into WordPress for each user. 13 | 14 | == Description == 15 | 16 | WP Bouncer will make sure users are logged in from only one device at a time. This should deter people from sharing their login credentials for your site, which is especially good for paid membership sites. 17 | 18 | WP Bouncer works by: 19 | * Storing a random "FAKESESSID" for each user when they log in. 20 | * If a user is logged in, on each page load (init hook), WP Bouncer checks if the FAKESESSID stored in the user's cookies is the same as the last login stored in a transient (fakesessid_user_login). 21 | * If not, WP Bouncer logs the user out and redirects them to a warning message. 22 | 23 | For Example: 24 | * User A logs in as "user". Their FAKESESSID, say "SESSION_A" is stored in a WordPress option. 25 | * User B logs in as "user". Their FAKESESSID, say "SESSION_B" is overwrites the stored WordPress option. 26 | * User A tries to load a page on your site, WP Bouncer catches them and logs them out, redirecting them to the warning message. 27 | * User B can browse around the site as normal... unless... 28 | * User A logs in again as "user". Their FAKESESSID, SESSION_A_v2 is stored in the WordPress option. 29 | * Now user B would be logged out if they load another page. 30 | 31 | Improvements: 32 | * Settings page to choose where users are taken after being bounced. 33 | * Keep track of how many bounces there are and lock the account down if there are so many in a small time frame. 34 | 35 | == Installation == 36 | 37 | This section describes how to install the plugin and get it working. 38 | 39 | 1. Upload `wp-bouncer` to the `/wp-content/plugins/` directory. 40 | 2. Activate the plugin through the 'Plugins' menu in WordPress. 41 | 3. That's it! There are no settings for this plugin. 42 | 43 | == Frequently Asked Questions == 44 | 45 | = What's this all about? = 46 | 47 | Something 48 | 49 | == Screenshots == 50 | 51 | 1. Something 52 | 53 | 54 | == Changelog == 55 | 56 | = 1.0.1 = 57 | * Fixed bug with how transients were being set and get. 58 | * Removed code in track_login that made sure you were logging in from login page. This will allow wp bouncer to kick in when logging in via wp_signon, etc. 59 | * Moved redirect url to a class property. Will eventually add a settings page for this and any other setting/configuration value. 60 | 61 | = 1.0 = 62 | * First release! 63 | 64 | 65 | == Upgrade Notice == 66 | 67 | = 1.0 = 68 | * First release! 69 | -------------------------------------------------------------------------------- /wp-bouncer.php: -------------------------------------------------------------------------------- 1 | redirect = esc_url_raw( plugin_dir_url( __FILE__ ) . 'login-warning.php' ); 47 | } 48 | 49 | /** 50 | * helper function to get browser data at login 51 | * 52 | * @return WP_Bouncer 53 | */ 54 | 55 | private function browser_data() { 56 | 57 | // grab base user agent and parse out 58 | $u_agent = $_SERVER['HTTP_USER_AGENT']; 59 | $bname = 'Unknown'; 60 | $platform = 'Unknown'; 61 | $version = ''; 62 | 63 | // determine platform 64 | if (preg_match('/linux/i', $u_agent)) 65 | $platform = 'linux'; 66 | 67 | if (preg_match('/macintosh|mac os x/i', $u_agent)) 68 | $platform = 'mac'; 69 | 70 | if (preg_match('/windows|win32/i', $u_agent)) 71 | $platform = 'windows'; 72 | 73 | 74 | // get browser info 75 | if(preg_match('/MSIE/i',$u_agent) && !preg_match('/Opera/i',$u_agent)) { 76 | $bname = 'Internet Explorer'; 77 | $ub = 'MSIE'; 78 | } 79 | 80 | if(preg_match('/Firefox/i',$u_agent)) { 81 | $bname = 'Mozilla Firefox'; 82 | $ub = 'Firefox'; 83 | } 84 | 85 | if(preg_match('/Chrome/i',$u_agent)) { 86 | $bname = 'Google Chrome'; 87 | $ub = 'Chrome'; 88 | } 89 | 90 | if(preg_match('/Safari/i',$u_agent) && !preg_match('/Chrome/i',$u_agent)) { 91 | $bname = 'Apple Safari'; 92 | $ub = 'Safari'; 93 | } 94 | 95 | if(preg_match('/Opera/i',$u_agent)) { 96 | $bname = 'Opera'; 97 | $ub = 'Opera'; 98 | } 99 | 100 | if(preg_match('/Netscape/i',$u_agent)) { 101 | $bname = 'Netscape'; 102 | $ub = 'Netscape'; 103 | } 104 | 105 | // finally get the correct version number 106 | $known = array('Version', $ub, 'other'); 107 | $pattern = '#(?' . join('|', $known) . ')[/ ]+(?[0-9.|a-zA-Z.]*)#'; 108 | 109 | if (!preg_match_all($pattern, $u_agent, $matches)) { 110 | // we have no matching number just continue 111 | } 112 | 113 | // see how many we have 114 | $i = count( $matches['browser'] ); 115 | if ($i != 1) { 116 | //we will have two since we are not using 'other' argument yet 117 | //see if version is before or after the name 118 | if (strripos( $u_agent, 'Version' ) < strripos($u_agent,$ub)){ 119 | $version= $matches['version'][0]; 120 | } 121 | else { 122 | $version= $matches['version'][1]; 123 | } 124 | } 125 | else { 126 | $version= $matches['version'][0]; 127 | } 128 | 129 | // check if we have a number 130 | if ($version == null || $version == '' ) 131 | $version = '?'; 132 | 133 | return array( 134 | 'userAgent' => $u_agent, 135 | 'name' => $bname, 136 | 'version' => $version, 137 | 'platform' => $platform, 138 | 'pattern' => $pattern 139 | ); 140 | } 141 | 142 | /** 143 | * redirect function for flagged logins 144 | * 145 | * @return WP_Bouncer 146 | */ 147 | 148 | public function flag_redirect() { 149 | 150 | $base = plugin_dir_url( __FILE__ ); 151 | wp_redirect( $this->redirect ); 152 | exit(); 153 | 154 | } 155 | 156 | /** 157 | * run checks for a flagged login 158 | * 159 | * @return WP_Bouncer 160 | */ 161 | 162 | public function login_flag() { 163 | 164 | if(is_user_logged_in()) 165 | { 166 | global $current_user; 167 | 168 | //check the fakesessid 169 | $fakesessid = get_transient("fakesessid_" . $current_user->user_login); 170 | 171 | //krumo("fakesessid_" . $current_user->user_login); 172 | //krumo($fakesessid); 173 | 174 | if(!empty($fakesessid)) 175 | { 176 | if(empty($_COOKIE['fakesessid']) || $fakesessid != $_COOKIE['fakesessid']) 177 | { 178 | //log user out 179 | wp_logout(); 180 | 181 | //redirect 182 | $this->flag_redirect(); 183 | } 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * track and set session data at login 190 | * 191 | * @return WP_Bouncer 192 | */ 193 | 194 | public function login_track($user_login) { 195 | // get browser data from current login 196 | $browser = $this->browser_data(); 197 | 198 | //store a "fake" session id in transient and cookie 199 | $fakesessid = md5($browser['name'] . $browser['platform'] . $_SERVER['REMOTE_ADDR'] . time()); 200 | set_transient("fakesessid_" . $user_login, $fakesessid, 3600*24*30); 201 | setcookie("fakesessid", $fakesessid, time()+3600*24*30, COOKIEPATH, COOKIE_DOMAIN, false); 202 | } 203 | 204 | /** 205 | * load textdomain 206 | * 207 | * @return WP_Bouncer 208 | */ 209 | 210 | 211 | public function textdomain() { 212 | 213 | load_plugin_textdomain( 'wp-bouncer', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); 214 | } 215 | 216 | 217 | /// end class 218 | } 219 | 220 | 221 | // Instantiate our class 222 | $WP_Bouncer = new WP_Bouncer(); 223 | --------------------------------------------------------------------------------