PluginProbe
Loggedin – Session Manager, Limit Concurrent Logins & Force Logout / 2.0
Loggedin – Session Manager, Limit Concurrent Logins & Force Logout v2.0
3.2.0 3.1.0 3.0.2 3.0.1 3.0.0 trunk 1.0.1 1.1.0 1.2.0 1.3.0 1.3.1 1.3.2 2.0 2.0.1 2.0.2 2.0.3 2.0.4
loggedin / includes / class-core.php

class-core.php in Loggedin – Session Manager, Limit Concurrent Logins & Force Logout 2.0, at includes/class-core.php

284 lines 6.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The main functionality of the plugin.
4 *
5 * @link https://duckdev.com/products/loggedin-limit-active-logins/
6 * @license http://www.gnu.org/licenses/ GNU General Public License
7 * @category Core
8 * @package Loggedin
9 * @subpackage Public
10 * @author Joel James <me@joelsays.com>
11 */
12
13 namespace DuckDev\Loggedin;
14
15 // If this file is called directly, abort.
16 defined( 'WPINC' ) || die;
17
18 use WP_Error;
19 use WP_Session_Tokens;
20
21 /**
22 * Class Core.
23 *
24 * @since 1.0.0
25 */
26 class Core {
27
28 /**
29 * Initialize the class and set its properties.
30 *
31 * We register all our common hooks here.
32 *
33 * @since 1.0.0
34 *
35 * @return void
36 */
37 public function __construct() {
38 // Use authentication filter.
39 add_filter( 'wp_authenticate_user', array( $this, 'validate_block_logic' ) );
40 // Use password check filter.
41 add_filter( 'check_password', array( $this, 'validate_allow_logic' ), 10, 4 );
42 }
43
44 /**
45 * Validate if the maximum active logins limit reached.
46 *
47 * This check happens only after authentication happens and
48 * the login logic is "Allow".
49 *
50 * @since 1.0.0
51 *
52 * @param boolean $check User Object/WPError.
53 * @param string $password Plaintext user's password.
54 * @param string $hash Hash of the user's password to check against.
55 * @param int $user_id User ID.
56 *
57 * @return bool
58 */
59 public function validate_allow_logic( $check, $password, $hash, $user_id ): bool {
60 // If the validation failed already, bail.
61 if ( ! $check ) {
62 return false;
63 }
64
65 // Get current logic.
66 $logic = get_option( 'loggedin_logic', 'allow' );
67
68 if ( in_array( $logic, array( 'allow', 'logout_oldest' ) ) ) {
69 // Continue only if limit reached.
70 if ( $this->has_limit_reached( $user_id ) ) {
71 if ( 'allow' === $logic ) {
72 // Destroy all others.
73 $this->destroy_all_sessions( $user_id );
74 } elseif ( 'logout_oldest' === $logic ) {
75 // Destroy oldest session.
76 $this->destroy_oldest_session( $user_id );
77 }
78 }
79 }
80
81 return true;
82 }
83
84
85 /**
86 * Validate if the maximum active logins limit reached.
87 *
88 * This check happens only after authentication happens and
89 * the login logic is "Block".
90 *
91 * @since 1.0.0
92 *
93 * @param object $user User Object/WPError.
94 *
95 * @return object User object or error object.
96 */
97 public function validate_block_logic( $user ) {
98 // If login validation failed already, return that error.
99 if ( is_wp_error( $user ) ) {
100 return $user;
101 }
102
103 $logic = get_option( 'loggedin_logic', 'allow' );
104
105 // Only when block method.
106 if ( 'block' === $logic ) {
107 // Check if limit exceed.
108 if ( $this->has_limit_reached( $user->ID ) ) {
109 /**
110 * Action hook to trigger when a login is blocked by loggedin.
111 *
112 * @since 2.0.0
113 *
114 * @param int $user_id User ID.
115 */
116 do_action( 'loggedin_login_blocked', $user->ID );
117
118 return new WP_Error( 'login_limit_reached', $this->limit_error_message() );
119 }
120 }
121
122 return $user;
123 }
124
125 /**
126 * Destroy all sessions of the user.
127 *
128 * @since 2.0.0
129 *
130 * @param int $user_id User ID.
131 *
132 * @return void
133 */
134 protected function destroy_all_sessions( int $user_id ) {
135 // Destroy all sessions.
136 WP_Session_Tokens::get_instance( $user_id )->destroy_all();
137
138 /**
139 * Action hook to trigger when all login sessions of a user are cleared by loggedin.
140 *
141 * @since 2.0.0
142 *
143 * @param int $user_id User ID.
144 */
145 do_action( 'loggedin_destroy_all_sessions', $user_id );
146 }
147
148 /**
149 * Log out only the oldest session for the user.
150 *
151 * This function retrieves the raw session tokens directly from user meta,
152 * identifies the oldest session by its login timestamp, and removes it.
153 * This will not work when a different type of session storage (eg: Redis) is being used.
154 *
155 * @since 2.0.0
156 *
157 * @param int $user_id User ID.
158 *
159 * @return void
160 */
161 protected function destroy_oldest_session( int $user_id ) {
162 // Retrieve the raw sessions array directly from user meta.
163 $sessions = get_user_meta( $user_id, 'session_tokens', true );
164 if ( ! is_array( $sessions ) || empty( $sessions ) ) {
165 return;
166 }
167
168 $oldest_token = '';
169 $oldest_time = time();
170
171 // Loop through sessions to find the oldest one.
172 foreach ( $sessions as $token => $session ) {
173 if ( isset( $session['login'] ) && $session['login'] < $oldest_time ) {
174 $oldest_time = $session['login'];
175 $oldest_token = $token;
176 }
177 }
178
179 if ( ! empty( $oldest_token ) ) {
180 // Destroy oldest session.
181 unset( $sessions[ $oldest_token ] );
182 update_user_meta( $user_id, 'session_tokens', $sessions );
183
184 /**
185 * Action hook to trigger when oldest login session of a user are cleared by loggedin.
186 *
187 * @since 2.0.0
188 *
189 * @param int $user_id User ID.
190 */
191 do_action( 'loggedin_destroy_oldest_session', $user_id );
192 }
193 }
194
195 /**
196 * Check if the current user is allowed for another login.
197 *
198 * Count all the active logins for the current user annd
199 * check if that exceeds the maximum login limit set.
200 *
201 * @since 1.0.0
202 *
203 * @param int $user_id User ID.
204 *
205 * @return boolean Limit reached or not
206 */
207 protected function has_limit_reached( int $user_id ): bool {
208 // If bypassed.
209 if ( $this->is_bypassed( $user_id ) ) {
210 return false;
211 }
212
213 // Get maximum active logins allowed.
214 $maximum = intval( get_option( 'loggedin_maximum', 1 ) );
215
216 // Sessions token instance.
217 $manager = WP_Session_Tokens::get_instance( $user_id );
218
219 // Count sessions.
220 $count = count( $manager->get_all() );
221
222 // Check if limit reached.
223 $reached = $count >= $maximum;
224
225 /**
226 * Filter hook to change the limit condition.
227 *
228 * @since 1.3.0
229 * @since 1.3.1 Added count param.
230 *
231 * @param bool $reached Reached.
232 * @param int $user_id User ID.
233 * @param int $count Active logins count.
234 */
235 return apply_filters( 'loggedin_reached_limit', $reached, $user_id, $count );
236 }
237
238 /**
239 * Custom login limit bypassing.
240 *
241 * Filter to bypass login limit based on a condition.
242 * You can make use of this filter if you want to bypass
243 * some users or roles from limit limit.
244 *
245 * @since 1.0.0
246 *
247 * @param int $user_id User ID.
248 *
249 * @return bool
250 */
251 protected function is_bypassed( int $user_id ): bool {
252 /**
253 * Filter hook to bypass the check.
254 *
255 * @since 1.0.0
256 *
257 * @param int $user_id User ID.
258 * @param bool $bypass Bypassed.
259 */
260 return (bool) apply_filters( 'loggedin_bypass', false, $user_id );
261 }
262
263 /**
264 * Error message text if user active logins count is maximum
265 *
266 * @since 1.0.0
267 *
268 * @return string Error message
269 */
270 protected function limit_error_message(): string {
271 // Error message.
272 $message = __( 'You\'ve reached the maximum number of active logins for this account. Please log out from another device to continue.', 'loggedin' );
273
274 /**
275 * Filter hook to change the error message.
276 *
277 * @since 1.0.0
278 *
279 * @param string $message Message.
280 */
281 return apply_filters( 'loggedin_error_message', $message );
282 }
283 }
284