$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;
}
}