PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.9
4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 All 140 releases
learnpress / inc / jwt / includes / class-jwt-public.php

class-jwt-public.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.9, at inc/jwt/includes/class-jwt-public.php

403 lines 10.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use LP\Firebase\JWT\JWT;
4
5 /**
6 * REST API: LP_Jwt_Public
7 *
8 * @package LPJWTAuth
9 * @since 1.0.0
10 * @author Nhamdv <[email protected]>
11 */
12
13 class LP_Jwt_Public {
14 private $name;
15
16 private $version;
17
18 private $namespace;
19
20 private $jwt_error;
21
22 private $secret_key;
23
24 public function __construct( $name, $version ) {
25 $this->name = $name;
26 $this->version = $version;
27 $this->namespace = $this->name . '/' . $this->version;
28
29 if ( ! defined( 'SECURE_AUTH_KEY' ) && ! defined( 'LP_SECURE_AUTH_KEY' ) ) {
30 return;
31 }
32
33 $this->secret_key = defined( 'LP_SECURE_AUTH_KEY' ) ? LP_SECURE_AUTH_KEY : SECURE_AUTH_KEY;
34 }
35
36 public function register_routes() {
37 register_rest_route(
38 $this->namespace,
39 'token',
40 array(
41 'methods' => WP_REST_Server::CREATABLE,
42 'callback' => array( $this, 'generate_token' ),
43 'args' => array(
44 'username' => array(
45 'description' => esc_html__( 'The username of the user.', 'learnpress' ),
46 'type' => 'string',
47 'sanitize_callback' => 'sanitize_text_field',
48 'validate_callback' => 'rest_validate_request_arg',
49 ),
50 'password' => array(
51 'description' => esc_html__( 'The password of the user.', 'learnpress' ),
52 'type' => 'string',
53 'sanitize_callback' => 'sanitize_text_field',
54 'validate_callback' => 'rest_validate_request_arg',
55 ),
56 ),
57 'schema' => array( $this, 'get_item_schema' ),
58 'permission_callback' => '__return_true',
59 )
60 );
61
62 register_rest_route(
63 $this->namespace,
64 'token/validate',
65 array(
66 'methods' => WP_REST_Server::CREATABLE,
67 'callback' => array( $this, 'validate_token' ),
68 'permission_callback' => '__return_true',
69 )
70 );
71
72 register_rest_route(
73 $this->namespace,
74 'token/register',
75 array(
76 'methods' => WP_REST_Server::CREATABLE,
77 'callback' => array( $this, 'register' ),
78 'permission_callback' => '__return_true',
79 )
80 );
81 }
82
83 public function get_item_schema() {
84 $schema = array(
85 '$schema' => 'http://json-schema.org/draft-04/schema#',
86 'title' => esc_html__( 'JSON Web Token', 'learnpress' ),
87 'type' => 'object',
88 'properties' => array(
89 'token' => array(
90 'description' => esc_html__( 'JSON Web Token.', 'learnpress' ),
91 'type' => 'string',
92 'readonly' => true,
93 ),
94 'user_id' => array(
95 'description' => esc_html__( 'The ID of the user.', 'learnpress' ),
96 'type' => 'integer',
97 'readonly' => true,
98 ),
99 'user_login' => array(
100 'description' => esc_html__( 'The username of the user', 'learnpress' ),
101 'type' => 'string',
102 'readonly' => true,
103 ),
104 'user_email' => array(
105 'description' => esc_html__( 'The email address of the user.', 'learnpress' ),
106 'type' => 'string',
107 'readonly' => true,
108 ),
109 ),
110 );
111
112 return apply_filters( 'lp_rest_authentication_token_schema', $schema );
113 }
114
115 /**
116 * Add CORs support to the request.
117 */
118 public function add_cors_support() {
119 $enable_cors = defined( 'LP_JWT_AUTH_CORS_ENABLE' ) ? LP_JWT_AUTH_CORS_ENABLE : false;
120
121 if ( $enable_cors ) {
122 $headers = apply_filters( 'lp_jwt_auth_cors_allow_headers', 'Access-Control-Allow-Headers, Content-Type, Authorization' );
123 header( sprintf( 'Access-Control-Allow-Headers: %s', $headers ) );
124 }
125 }
126
127 public function register( WP_REST_Request $request ) {
128 $username = $request->get_param( 'username' );
129 $password = $request->get_param( 'password' );
130 $confirm_password = $request->get_param( 'confirm_password' );
131 $email = $request->get_param( 'email' );
132
133 $customer_id = LP_Forms_Handler::learnpress_create_new_customer( $email, $username, $password, $confirm_password );
134
135 if ( is_wp_error( $customer_id ) ) {
136 return new WP_Error(
137 $customer_id->get_error_code(),
138 $customer_id->get_error_message(),
139 array(
140 'status' => 403,
141 )
142 );
143 }
144
145 return $this->generate_token( $request );
146 }
147
148 public function generate_token( WP_REST_Request $request ) {
149 $secret_key = $this->secret_key;
150 $username = $request->get_param( 'username' );
151 $password = $request->get_param( 'password' );
152
153 if ( ! $secret_key ) {
154 return new WP_Error(
155 'lp_jwt_auth_bad_config',
156 esc_html__( 'LearnPress JWT is not configurated properly, please contact the admin', 'learnpress' ),
157 array(
158 'status' => 403,
159 )
160 );
161 }
162
163 /** Try to authenticate the user with the passed credentials*/
164 $user = wp_authenticate( $username, $password );
165
166 /** If the authentication fails return a error*/
167 if ( is_wp_error( $user ) ) {
168 $error_code = $user->get_error_code();
169
170 return new WP_Error(
171 '[lp_jwt_auth] ' . $error_code,
172 $user->get_error_message( $error_code ),
173 array(
174 'status' => 403,
175 )
176 );
177 }
178
179 /** Valid credentials, the user exists create the according Token */
180 $issued_at = time();
181 $not_before = apply_filters( 'lp_jwt_auth_not_before', $issued_at, $issued_at );
182 $expire = apply_filters( 'lp_jwt_auth_expire', $issued_at + WEEK_IN_SECONDS, $issued_at );
183
184 $token = array(
185 'iss' => get_bloginfo( 'url' ),
186 'iat' => $issued_at,
187 'nbf' => $not_before,
188 'exp' => $expire,
189 'data' => array(
190 'user' => array(
191 'id' => $user->data->ID,
192 ),
193 ),
194 );
195
196 /** Let the user modify the token data before the sign. */
197 $token = JWT::encode( apply_filters( 'lp_jwt_auth_token_before_sign', $token, $user ), $secret_key );
198
199 /** The token is signed, now create the object with no sensible user data to the client*/
200 $data = array(
201 'token' => $token,
202 'user_id' => $user->data->ID,
203 'user_login' => $user->data->user_login,
204 'user_email' => $user->data->user_email,
205 'user_display_name' => $user->data->display_name,
206 );
207
208 return apply_filters( 'lp_jwt_auth_token_before_dispatch', $data, $user );
209 }
210
211 /**
212 * This is our Middleware to try to authenticate the user according to the
213 * token send.
214 *
215 * @param (int|bool) $user Logged User ID
216 *
217 * @return (int|bool)
218 */
219 public function determine_current_user( $user_id ) {
220 if ( ! empty( $user_id ) ) {
221 return $user_id;
222 }
223
224 $rest_prefix = trailingslashit( rest_get_url_prefix() );
225 $request_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) );
226
227 /**
228 * Only process REST requests.
229 */
230 if ( strpos( $request_uri, $rest_prefix ) === false ) {
231 return $user_id;
232 }
233
234 /*
235 * Skip the token endpoint itself to avoid double validation.
236 */
237 if ( strpos( $request_uri, $rest_prefix . $this->namespace . '/token' ) !== false ) {
238 return $user_id;
239 }
240
241 /*
242 * No Authorization header → let other auth methods (cookie, app passwords) handle it.
243 */
244 $has_auth = ! empty( $_SERVER['HTTP_AUTHORIZATION'] ) || ! empty( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] );
245 if ( ! $has_auth ) {
246 return $user_id;
247 }
248
249 /** Public LP endpoints that should not surface auth errors. */
250 $is_public_lp = (bool) ( strpos( $request_uri, '/courses' ) || strpos( $request_uri, '/reset-password' ) || strpos( $request_uri, '/course_category' ) || strpos( $request_uri, '/sections/' ) || strpos( $request_uri, '/section-items/' ) || strpos( $request_uri, '/users' ) );
251
252 $is_lp_api = strpos( $request_uri, $rest_prefix . $this->name . '/' ) !== false;
253
254 $token = $this->validate_token( false );
255
256 if ( is_wp_error( $token ) ) {
257 if ( $is_lp_api && ! $is_public_lp ) {
258 $this->jwt_error = $token;
259 }
260
261 return $user_id;
262 }
263
264 return $token->data->user->id;
265 }
266
267 public function validate_token( $output = true ) {
268 /*
269 * Looking for the HTTP_AUTHORIZATION header, if not present just
270 * return the user.
271 */
272 $auth = isset( $_SERVER['HTTP_AUTHORIZATION'] ) ? sanitize_text_field( $_SERVER['HTTP_AUTHORIZATION'] ) : false;
273
274 /* Double check for different auth header string (server dependent) */
275 if ( ! $auth ) {
276 $auth = isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ? sanitize_text_field( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) : false;
277 }
278
279 if ( ! $auth ) {
280 return new WP_Error(
281 'lp_jwt_auth_no_auth_header',
282 esc_html__( 'Authorization header not found.', 'learnpress' ),
283 array(
284 'status' => 401,
285 )
286 );
287 }
288
289 /*
290 * The HTTP_AUTHORIZATION is present verify the format
291 * if the format is wrong return the user.
292 */
293 list( $token ) = sscanf( $auth, 'Bearer %s' );
294
295 if ( ! $token ) {
296 return new WP_Error(
297 'lp_jwt_auth_bad_auth_header',
298 esc_html__( 'Authentication token is missing.', 'learnpress' ),
299 array(
300 'status' => 401,
301 )
302 );
303 }
304
305 /** Get the Secret Key */
306 $secret_key = $this->secret_key;
307
308 if ( ! $secret_key ) {
309 return new WP_Error(
310 'lp_jwt_auth_bad_config',
311 esc_html__( 'LearnPress JWT is not configurated properly, please contact the admin', 'learnpress' ),
312 array(
313 'status' => 401,
314 )
315 );
316 }
317
318 /** Try to decode the token */
319 try {
320 $token = JWT::decode( $token, $secret_key, array( 'HS256' ) );
321
322 /** The Token is decoded now validate the iss */
323 if ( $token->iss != get_bloginfo( 'url' ) ) {
324 return new WP_Error(
325 'lp_jwt_auth_bad_iss',
326 esc_html__( 'The iss do not match with this server', 'learnpress' ),
327 array(
328 'status' => 401,
329 )
330 );
331 }
332
333 /** So far so good, validate the user id in the token */
334 if ( ! isset( $token->data->user->id ) ) {
335 return new WP_Error(
336 'lp_jwt_auth_bad_request',
337 esc_html__( 'User ID not found in the token', 'learnpress' ),
338 array(
339 'status' => 401,
340 )
341 );
342 }
343
344 if ( ! isset( $token->exp ) ) {
345 return new WP_Error(
346 'rest_authentication_missing_token_expiration',
347 esc_html__( 'The token must have an expiration date.', 'learnpress' ),
348 array(
349 'status' => 401,
350 )
351 );
352 }
353
354 if ( time() > $token->exp ) {
355 return new WP_Error(
356 'rest_authentication_token_expired',
357 esc_html__( 'The token has expired.', 'learnpress' ),
358 array(
359 'status' => 401,
360 )
361 );
362 }
363
364 /** Everything looks good return the decoded token if the $output is false */
365 if ( ! $output ) {
366 return $token;
367 }
368
369 /** If the output is true return an answer to the request to show it */
370 return array(
371 'code' => 'lp_jwt_auth_valid_token',
372 'message' => esc_html__( 'Valid access token.', 'learnpress' ),
373 'data' => array(
374 'status' => 200,
375 'exp' => $token->exp - time(),
376 ),
377 );
378 } catch ( Exception $e ) {
379 return new WP_Error(
380 'lp_jwt_auth_invalid_token',
381 $e->getMessage(),
382 array(
383 'status' => 401,
384 )
385 );
386 }
387 }
388
389 /**
390 * Filter to hook the rest_pre_dispatch, if the is an error in the request
391 * send it, if there is no error just continue with the current request.
392 *
393 * @param $request
394 */
395 public function rest_pre_dispatch( $request ) {
396 if ( is_wp_error( $this->jwt_error ) ) {
397 return $this->jwt_error;
398 }
399
400 return $request;
401 }
402 }
403