PluginProbe
Authorizer / 2.9.3
Authorizer v2.9.3
3.15.3 3.15.2 3.15.1 3.15.0 3.14.3 3.14.4 3.14.2 3.14.1 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.9.0 2.9.1 2.9.10 2.9.11 2.9.12 2.9.13 2.9.2 2.9.3 2.9.6 All 126 releases
authorizer / src / authorizer / class-login-form.php

class-login-form.php in Authorizer 2.9.3, at src/authorizer/class-login-form.php

391 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Authorizer
4 *
5 * @license GPL-2.0+
6 * @link https://github.com/uhm-coe/authorizer
7 * @package authorizer
8 */
9
10 namespace Authorizer;
11
12 use Authorizer\Helper;
13 use Authorizer\Options;
14
15 /**
16 * Contains modifications to the WordPress login form.
17 */
18 class Login_Form extends Static_Instance {
19
20 /**
21 * Load external resources for the public-facing site.
22 *
23 * Action: wp_enqueue_scripts
24 */
25 public function auth_public_scripts() {
26 // Load (and localize) public scripts.
27 $options = Options::get_instance();
28 $current_path = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : home_url();
29 wp_enqueue_script( 'auth_public_scripts', plugins_url( '/js/authorizer-public.js', plugin_root() ), array( 'jquery' ), '2.8.0', false );
30 $auth_localized = array(
31 'wpLoginUrl' => wp_login_url( $current_path ),
32 'publicWarning' => get_option( 'auth_settings_advanced_public_notice' ),
33 'anonymousNotice' => $options->get( 'access_redirect_to_message' ),
34 'logIn' => esc_html__( 'Log In', 'authorizer' ),
35 );
36 wp_localize_script( 'auth_public_scripts', 'auth', $auth_localized );
37
38 // Load public css.
39 wp_register_style( 'authorizer-public-css', plugins_url( 'css/authorizer-public.css', plugin_root() ), array(), '2.8.0' );
40 wp_enqueue_style( 'authorizer-public-css' );
41 }
42
43
44 /**
45 * Enqueue JS scripts and CSS styles appearing on wp-login.php.
46 *
47 * Action: login_enqueue_scripts
48 *
49 * @return void
50 */
51 public function login_enqueue_scripts_and_styles() {
52 // Grab plugin settings.
53 $options = Options::get_instance();
54 $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
55
56 // Enqueue scripts appearing on wp-login.php.
57 wp_enqueue_script( 'auth_login_scripts', plugins_url( '/js/authorizer-login.js', plugin_root() ), array( 'jquery' ), '2.8.0', false );
58
59 // Enqueue styles appearing on wp-login.php.
60 wp_register_style( 'authorizer-login-css', plugins_url( '/css/authorizer-login.css', plugin_root() ), array(), '2.8.0' );
61 wp_enqueue_style( 'authorizer-login-css' );
62
63 /**
64 * Developers can use the `authorizer_add_branding_option` filter
65 * to add a radio button for "Custom WordPress login branding"
66 * under the "Advanced" tab in Authorizer options. Example:
67 * function my_authorizer_add_branding_option( $branding_options ) {
68 * $new_branding_option = array(
69 * 'value' => 'your_brand'
70 * 'description' => 'Custom Your Brand Login Screen',
71 * 'css_url' => 'http://url/to/your_brand.css',
72 * 'js_url' => 'http://url/to/your_brand.js',
73 * );
74 * array_push( $branding_options, $new_branding_option );
75 * return $branding_options;
76 * }
77 * add_filter( 'authorizer_add_branding_option', 'my_authorizer_add_branding_option' );
78 */
79 $branding_options = array();
80 $branding_options = apply_filters( 'authorizer_add_branding_option', $branding_options );
81 foreach ( $branding_options as $branding_option ) {
82 // Make sure the custom brands have the required values.
83 if ( ! ( is_array( $branding_option ) && array_key_exists( 'value', $branding_option ) && array_key_exists( 'css_url', $branding_option ) && array_key_exists( 'js_url', $branding_option ) ) ) {
84 continue;
85 }
86 if ( $auth_settings['advanced_branding'] === $branding_option['value'] ) {
87 wp_enqueue_script( 'auth_login_custom_scripts-' . sanitize_title( $branding_option['value'] ), $branding_option['js_url'], array( 'jquery' ), '2.8.0', false );
88 wp_register_style( 'authorizer-login-custom-css-' . sanitize_title( $branding_option['value'] ), $branding_option['css_url'], array(), '2.8.0' );
89 wp_enqueue_style( 'authorizer-login-custom-css-' . sanitize_title( $branding_option['value'] ) );
90 }
91 }
92
93 // If we're using Google logins, load those resources.
94 if ( '1' === $auth_settings['google'] ) {
95 wp_enqueue_script( 'authorizer-login-custom-google', plugins_url( '/js/authorizer-login-custom_google.js', plugin_root() ), array( 'jquery' ), '2.8.0', false ); ?>
96 <meta name="google-signin-clientid" content="<?php echo esc_attr( $auth_settings['google_clientid'] ); ?>" />
97 <meta name="google-signin-scope" content="email" />
98 <meta name="google-signin-cookiepolicy" content="single_host_origin" />
99 <?php
100 }
101 }
102
103
104 /**
105 * Load external resources in the footer of the wp-login.php page.
106 *
107 * Action: login_footer
108 */
109 public function load_login_footer_js() {
110 // Grab plugin settings.
111 $options = Options::get_instance();
112 $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
113 $ajaxurl = admin_url( 'admin-ajax.php' );
114 if ( '1' === $auth_settings['google'] ) :
115 ?>
116 <script type="text/javascript">
117 /* global location, window */
118 // Reload login page if reauth querystring param exists,
119 // since reauth interrupts external logins (e.g., google).
120 if ( location.search.indexOf( 'reauth=1' ) >= 0 ) {
121 location.href = location.href.replace( 'reauth=1', '' );
122 }
123
124 // eslint-disable-next-line no-implicit-globals
125 function authUpdateQuerystringParam( uri, key, value ) {
126 var re = new RegExp( '([?&])' + key + '=.*?(&|$)', 'i' );
127 var separator = uri.indexOf( '?' ) !== -1 ? '&' : '?';
128 if ( uri.match( re ) ) {
129 return uri.replace( re, '$1' + key + '=' + value + '$2' );
130 } else {
131 return uri + separator + key + '=' + value;
132 }
133 }
134
135 // eslint-disable-next-line
136 function signInCallback( authResult ) { // jshint ignore:line
137 var $ = jQuery;
138 if ( authResult.status && authResult.status.signed_in ) {
139 // Hide the sign-in button now that the user is authorized, for example:
140 $( '#googleplus_button' ).attr( 'style', 'display: none' );
141
142 // Send the code to the server
143 var ajaxurl = '<?php echo esc_attr( $ajaxurl ); ?>';
144 $.post(ajaxurl, {
145 action: 'process_google_login',
146 code: authResult.code,
147 nonce: $('#nonce_google_auth-<?php echo esc_attr( Helper::get_cookie_value() ); ?>' ).val(),
148 }, function() {
149 // Handle or verify the server response if necessary.
150 // console.log( response );
151
152 // Reload wp-login.php to continue the authentication process.
153 var newHref = authUpdateQuerystringParam( location.href, 'external', 'google' );
154 if ( location.href === newHref ) {
155 location.reload();
156 } else {
157 location.href = newHref;
158 }
159 });
160 } else {
161 // Update the app to reflect a signed out user
162 // Possible error values:
163 // "user_signed_out" - User is signed-out
164 // "access_denied" - User denied access to your app
165 // "immediate_failed" - Could not automatically log in the user
166 // console.log('Sign-in state: ' + authResult['error']);
167
168 // If user denies access, reload the login page.
169 if ( authResult.error === 'access_denied' || authResult.error === 'user_signed_out' ) {
170 window.location.reload();
171 }
172 }
173 }
174 </script>
175 <?php
176 endif;
177 }
178
179
180 /**
181 * Create links for any external authentication services that are enabled.
182 *
183 * Action: login_form
184 */
185 public function login_form_add_external_service_links() {
186 // Grab plugin settings.
187 $options = Options::get_instance();
188 $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
189 ?>
190 <div id="auth-external-service-login">
191 <?php if ( '1' === $auth_settings['google'] ) : ?>
192 <p><a id="googleplus_button" class="button button-primary button-external button-google"><span class="dashicons dashicons-googleplus"></span><span class="label"><?php esc_html_e( 'Sign in with Google', 'authorizer' ); ?></span></a></p>
193 <?php wp_nonce_field( 'google_csrf_nonce', 'nonce_google_auth-' . Helper::get_cookie_value() ); ?>
194 <?php endif; ?>
195
196 <?php if ( '1' === $auth_settings['cas'] ) : ?>
197 <p><a class="button button-primary button-external button-cas" href="<?php echo esc_attr( Helper::modify_current_url_for_cas_login() ); ?>">
198 <span class="dashicons dashicons-lock"></span>
199 <span class="label">
200 <?php
201 echo esc_html(
202 sprintf(
203 /* TRANSLATORS: %s: Custom CAS label from authorizer options */
204 __( 'Sign in with %s', 'authorizer' ),
205 $auth_settings['cas_custom_label']
206 )
207 );
208 ?>
209 </span>
210 </a></p>
211 <?php endif; ?>
212
213 <?php if ( isset( $auth_settings['advanced_hide_wp_login'] ) && '1' === $auth_settings['advanced_hide_wp_login'] && isset( $_SERVER['QUERY_STRING'] ) && false === strpos( $_SERVER['QUERY_STRING'], 'external=wordpress' ) ) : // phpcs:ignore WordPress.Security.ValidatedSanitizedInput ?>
214 <style type="text/css">
215 body.login-action-login form {
216 padding-bottom: 8px;
217 }
218 body.login-action-login form p > label,
219 body.login-action-login form .forgetmenot,
220 body.login-action-login form .submit,
221 body.login-action-login #nav { /* csslint allow: ids */
222 display: none;
223 }
224 </style>
225 <?php elseif ( '1' === $auth_settings['cas'] || '1' === $auth_settings['google'] ) : ?>
226 <h3> &mdash; <?php esc_html_e( 'or', 'authorizer' ); ?> &mdash; </h3>
227 <?php endif; ?>
228 </div>
229 <?php
230 }
231
232
233 /**
234 * Redirect to CAS login when visiting login page (only if option is
235 * enabled, CAS is the only service, and WordPress logins are hidden).
236 * Note: hook into wp_login_errors filter so this fires after the
237 * authenticate hook (where the redirect to CAS happens), but before html
238 * output is started (so the redirect header doesn't complain about data
239 * already being sent).
240 *
241 * Filter: wp_login_errors
242 *
243 * @param object $errors WP Error object.
244 * @param string $redirect_to Where to redirect on error.
245 * @return WP_Error|void WP Error object or void on redirect.
246 */
247 public function wp_login_errors__maybe_redirect_to_cas( $errors, $redirect_to ) {
248 // Grab plugin settings.
249 $options = Options::get_instance();
250 $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
251
252 // Check whether we should redirect to CAS.
253 if (
254 isset( $_SERVER['QUERY_STRING'] ) &&
255 strpos( $_SERVER['QUERY_STRING'], 'external=wordpress' ) === false && // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
256 array_key_exists( 'cas_auto_login', $auth_settings ) && '1' === $auth_settings['cas_auto_login'] &&
257 array_key_exists( 'cas', $auth_settings ) && '1' === $auth_settings['cas'] &&
258 ( ! array_key_exists( 'ldap', $auth_settings ) || '1' !== $auth_settings['ldap'] ) &&
259 ( ! array_key_exists( 'google', $auth_settings ) || '1' !== $auth_settings['google'] ) &&
260 array_key_exists( 'advanced_hide_wp_login', $auth_settings ) && '1' === $auth_settings['advanced_hide_wp_login']
261 ) {
262 wp_redirect( Helper::modify_current_url_for_cas_login() ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
263 exit;
264 }
265
266 return $errors;
267 }
268
269
270 /**
271 * Set a unique cookie to add to Google auth nonce to avoid CSRF detection.
272 * Note: hook into login_init so this fires at the start of the visit to
273 * wp-login.php, but before any html output is started (so setting the
274 * cookie header doesn't complain about data already being sent).
275 *
276 * Action: login_init
277 *
278 * @return void
279 */
280 public function login_init__maybe_set_google_nonce_cookie() {
281 // Grab plugin settings.
282 $options = Options::get_instance();
283 $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
284
285 // If Google logins are enabled, make sure the cookie is set.
286 if ( array_key_exists( 'google', $auth_settings ) && '1' === $auth_settings['google'] ) {
287 if ( ! isset( $_COOKIE['login_unique'] ) ) {
288 setcookie( 'login_unique', Helper::get_cookie_value(), time() + 1800, '/', defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : '' );
289 $_COOKIE['login_unique'] = Helper::get_cookie_value();
290 }
291 }
292 }
293
294
295 /**
296 * Implements hook: do_action( 'wp_login_failed', $username );
297 * Update the user meta for the user that just failed logging in.
298 * Keep track of time of last failed attempt and number of failed attempts.
299 *
300 * Action: wp_login_failed
301 *
302 * @param string $username Username to update login count for.
303 * @return void
304 */
305 public function update_login_failed_count( $username ) {
306 // Grab plugin settings.
307 $options = Options::get_instance();
308 $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
309
310 // Get user trying to log in.
311 // If this isn't a real user, update the global failed attempt
312 // variables. We'll use these global variables to institute the
313 // lockouts on nonexistent accounts. We do this so an attacker
314 // won't be able to determine which accounts are real by which
315 // accounts get locked out on multiple invalid attempts.
316 $user = get_user_by( 'login', $username );
317
318 if ( false !== $user ) {
319 $last_attempt = get_user_meta( $user->ID, 'auth_settings_advanced_lockouts_time_last_failed', true );
320 $num_attempts = get_user_meta( $user->ID, 'auth_settings_advanced_lockouts_failed_attempts', true );
321 } else {
322 $last_attempt = get_option( 'auth_settings_advanced_lockouts_time_last_failed' );
323 $num_attempts = get_option( 'auth_settings_advanced_lockouts_failed_attempts' );
324 }
325
326 // Make sure $last_attempt (time) and $num_attempts are positive integers.
327 // Note: this addresses resetting them if either is unset from above.
328 $last_attempt = abs( intval( $last_attempt ) );
329 $num_attempts = abs( intval( $num_attempts ) );
330
331 // Reset the failed attempt count if the time since the last
332 // failed attempt is greater than the reset duration.
333 $time_since_last_fail = time() - $last_attempt;
334 $reset_duration = $auth_settings['advanced_lockouts']['reset_duration'] * 60; // minutes to seconds.
335 if ( $time_since_last_fail > $reset_duration ) {
336 $num_attempts = 0;
337 }
338
339 // Set last failed time to now and increment last failed count.
340 if ( false !== $user ) {
341 update_user_meta( $user->ID, 'auth_settings_advanced_lockouts_time_last_failed', time() );
342 update_user_meta( $user->ID, 'auth_settings_advanced_lockouts_failed_attempts', $num_attempts + 1 );
343 } else {
344 update_option( 'auth_settings_advanced_lockouts_time_last_failed', time() );
345 update_option( 'auth_settings_advanced_lockouts_failed_attempts', $num_attempts + 1 );
346 }
347 }
348
349
350 /**
351 * Overwrite the URL for the lost password link on the login form.
352 * If we're authenticating against an external service, standard
353 * WordPress password resets won't work.
354 *
355 * Filter: lostpassword_url
356 *
357 * @param string $lostpassword_url URL to reset password.
358 * @return string URL to reset password.
359 */
360 public function custom_lostpassword_url( $lostpassword_url ) {
361 // Grab plugin settings.
362 $options = Options::get_instance();
363 $auth_settings = $options->get_all( Helper::SINGLE_CONTEXT, 'allow override' );
364
365 if (
366 array_key_exists( 'ldap_lostpassword_url', $auth_settings ) &&
367 filter_var( $auth_settings['ldap_lostpassword_url'], FILTER_VALIDATE_URL )
368 ) {
369 $lostpassword_url = $auth_settings['ldap_lostpassword_url'];
370 }
371 return $lostpassword_url;
372 }
373
374
375 /**
376 * Add custom error message to login screen.
377 *
378 * Filter: login_errors
379 *
380 * @param string $errors Error description.
381 * @return string Error description with Authorizer errors added.
382 */
383 public function show_advanced_login_error( $errors ) {
384 $error = get_option( 'auth_settings_advanced_login_error' );
385 delete_option( 'auth_settings_advanced_login_error' );
386 $errors = ' ' . $error . "<br />\n";
387 return $errors;
388 }
389
390 }
391