$user_id, 'context' => [ 'name' => $name ] ] ); if ( ! Helper::get_setting( 'app_pw_alert_on_create', true ) ) { return; } $user = get_userdata( $user_id ); Mailer::send( \GemSecurity\Mailer::recipient(), '🔐', __( 'New application password created', 'gemsecurity' ), [ [ 'type' => 'paragraph', 'text' => sprintf( /* translators: 1: password label, 2: username. */ __( 'An application password %1$s was just created for %2$s. Application passwords bypass the login form, two-factor and CAPTCHA.', 'gemsecurity' ), esc_html( $name ), esc_html( $user ? $user->user_login : (string) $user_id ) ), ], [ 'type' => 'paragraph', 'text' => __( 'If this was not expected, revoke it now.', 'gemsecurity' ), ], [ 'type' => 'button', 'url' => admin_url( 'admin.php?page=' . GEMSECURITY_PLUGIN_SLUG . '-api-hardening' ), 'label' => __( 'Review application passwords', 'gemsecurity' ), ], ] ); } /** * Delete application passwords older than the configured max age. */ public function prune_expired_app_passwords() { if ( ! self::app_passwords_supported() ) { return; } $days = (int) Helper::get_setting( 'app_pw_max_age_days', 0 ); if ( $days <= 0 ) { return; } $cutoff = time() - $days * DAY_IN_SECONDS; $user_ids = get_users( [ 'meta_key' => '_application_passwords', 'fields' => 'ID' ] ); // phpcs:ignore WordPress.DB.SlowDBQuery foreach ( $user_ids as $uid ) { $items = \WP_Application_Passwords::get_user_application_passwords( $uid ); foreach ( $items as $item ) { if ( ! empty( $item['created'] ) && (int) $item['created'] < $cutoff ) { \WP_Application_Passwords::delete_application_password( $uid, $item['uuid'] ); Logger::log( 'app_password_expired', sprintf( /* translators: 1: label, 2: user id, 3: age in days. */ __( 'Expired application password "%1$s" for user #%2$d (older than %3$d days).', 'gemsecurity' ), isset( $item['name'] ) ? $item['name'] : '', $uid, $days ), Logger::SEVERITY_NOTICE, [ 'user_id' => $uid ] ); } } } } /** * Throttle REST API requests per IP for non-privileged callers. * * @param mixed $result * @param \WP_REST_Server $server * @param \WP_REST_Request $request * @return mixed */ public function rest_rate_limit( $result, $server, $request ) { // Respect an already-computed response, and never throttle admins or the // plugin's own authenticated screens. if ( null !== $result || current_user_can( 'manage_options' ) ) { return $result; } $ip = Helper::get_ip_address(); if ( '0.0.0.0' === $ip ) { return $result; } $max = max( 1, (int) Helper::get_setting( 'rest_rate_limit_max', 120 ) ); $window = max( 1, (int) Helper::get_setting( 'rest_rate_limit_window', 1 ) ); // minutes. $key = self::RL_PREFIX . md5( $ip ); $count = (int) Store::get( $key ); if ( $count >= $max ) { return new \WP_Error( 'gemsecurity_rest_throttled', __( 'Too many requests. Please slow down.', 'gemsecurity' ), [ 'status' => 429 ] ); } Store::set( $key, $count + 1, $window * MINUTE_IN_SECONDS ); return $result; } }