diff --git a/Dockerfile b/Dockerfile index 4e88692..4480179 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ -FROM wordpress:6.1.0 +ARG wordpress_version + +FROM wordpress:$wordpress_version RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && chmod +x wp-cli.phar && mv wp-cli.phar /usr/local/bin/wp RUN apt-get update RUN apt-get -y install vim less diff --git a/README.md b/README.md index 72c99b7..99ad51d 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,11 @@ container: compose file. Defaults to the host machine's platform. The image used for the `db` service does not have `linux/arm64/v8` support. On such hosts (e.g. Apple Silicon Macs), set this to `linux/amd64`. +* `WORDPRESS_VERSION`: The WordPress version to use when building the docker +container. Must be a valid tag per https://hub.docker.com/_/wordpress/tags. +Defaults to `latest`. Note that only one container at a time is currently +supported, so when switching versions you'll need to remove the existing +dev container before running `docker-compose up -d` again. #### Wordpress CLI The wordpress installation includes the `wp` command line tool. Note that all diff --git a/class-duouniversal-settings.php b/class-duouniversal-settings.php index e87b93d..ce5fce9 100644 --- a/class-duouniversal-settings.php +++ b/class-duouniversal-settings.php @@ -1,31 +1,41 @@ duo_utils = $duo_utils; - $this->wordpress_helper = $duo_utils->wordpress_helper; + $this->duo_utils = $duo_utils; } function duo_settings_page() { $this->duo_utils->duo_debug_log( 'Displaying duo setting page' ); ?>

Duo Universal Two-Factor Authentication

- wordpress_helper->is_multisite() ) { ?> +
- wordpress_helper->settings_fields( 'duo_universal_settings' ); ?> - wordpress_helper->do_settings_sections( 'duo_universal_settings' ); ?> + +

- +

@@ -33,15 +43,15 @@ function duo_settings_page() { } function duo_settings_client_id() { - $client_id = $this->wordpress_helper->esc_attr( $this->duo_utils->duo_get_option( 'duoup_client_id' ) ); - echo ""; + $client_id = \esc_attr( $this->duo_utils->duo_get_option( 'duoup_client_id' ) ); + echo ""; } function duoup_client_id_validate( $client_id ) { - $client_id = $this->wordpress_helper->sanitize_text_field( $client_id ); + $client_id = sanitize_text_field( $client_id ); if ( strlen( $client_id ) !== 20 ) { - $this->wordpress_helper->add_settings_error( 'duoup_client_id', '', 'Client ID is not valid' ); - $current_id = $this->wordpress_helper->esc_attr( $this->duo_utils->duo_get_option( 'duoup_client_id' ) ); + \add_settings_error( 'duoup_client_id', '', 'Client ID is not valid' ); + $current_id = \esc_attr( $this->duo_utils->duo_get_option( 'duoup_client_id' ) ); if ( $current_id ) { return $current_id; } @@ -52,26 +62,26 @@ function duoup_client_id_validate( $client_id ) { } function duo_settings_client_secret() { - $client_secret = $this->wordpress_helper->esc_attr( $this->duo_utils->duo_get_option( 'duoup_client_secret' ) ); + $client_secret = \esc_attr( $this->duo_utils->duo_get_option( 'duoup_client_secret' ) ); if ( $client_secret ) { $value = SECRET_PLACEHOLDER; } else { $value = ''; } - echo ""; + echo ""; } function duoup_client_secret_validate( $client_secret ) { - $client_secret = $this->wordpress_helper->sanitize_text_field( $client_secret ); - $current_secret = $this->wordpress_helper->esc_attr( $this->duo_utils->duo_get_option( 'duoup_client_secret' ) ); + $client_secret = sanitize_text_field( $client_secret ); + $current_secret = \esc_attr( $this->duo_utils->duo_get_option( 'duoup_client_secret' ) ); if ( strlen( $client_secret ) !== 40 ) { - $this->wordpress_helper->add_settings_error( 'duoup_client_secret', '', 'Client secret is not valid' ); + \add_settings_error( 'duoup_client_secret', '', 'Client secret is not valid' ); if ( $current_secret ) { return $current_secret; } else { return ''; } - } elseif ( $client_secret === SECRET_PLACEHOLDER ) { + } elseif ( SECRET_PLACEHOLDER === $client_secret ) { return $current_secret; } else { return $client_secret; @@ -79,15 +89,15 @@ function duoup_client_secret_validate( $client_secret ) { } function duo_settings_host() { - $host = $this->wordpress_helper->esc_attr( $this->duo_utils->duo_get_option( 'duoup_api_host' ) ); - echo ""; + $host = \esc_attr( $this->duo_utils->duo_get_option( 'duoup_api_host' ) ); + echo ""; } function duoup_api_host_validate( $host ) { - $host = $this->wordpress_helper->sanitize_text_field( $host ); + $host = sanitize_text_field( $host ); if ( ! preg_match( '/^api-[a-zA-Z\d\.-]*/', $host ) || str_starts_with( $host, 'api-api-' ) ) { - $this->wordpress_helper->add_settings_error( 'duoup_api_host', '', 'Host is not valid' ); - $current_host = $this->wordpress_helper->esc_attr( $this->duo_utils->duo_get_option( 'duo_host' ) ); + \add_settings_error( 'duoup_api_host', '', 'Host is not valid' ); + $current_host = \esc_attr( $this->duo_utils->duo_get_option( 'duo_host' ) ); if ( $current_host ) { return $current_host; } @@ -98,9 +108,9 @@ function duoup_api_host_validate( $host ) { } function duo_settings_failmode() { - $failmode = $this->wordpress_helper->esc_attr( $this->duo_utils->duo_get_option( 'duoup_failmode', 'open' ) ); + $failmode = \esc_attr( $this->duo_utils->duo_get_option( 'duoup_failmode', 'open' ) ); echo '' . - $this->wordpress_helper->esc_html( $role ) . + \esc_html( $role ) . '
' ); } } @@ -157,7 +167,7 @@ function duoup_roles_validate( $options ) { if ( ! array_key_exists( $opt, $valid_roles ) ) { unset( $options[ $opt ] ); } else { - $options[ $opt ] = $this->wordpress_helper->sanitize_text_field( $value ); + $options[ $opt ] = sanitize_text_field( $value ); } } return $options; @@ -174,28 +184,28 @@ function duo_settings_xmlrpc() { if ( $this->duo_utils->duo_get_option( 'duoup_xmlrpc', 'off' ) === 'off' ) { $val = 'checked'; } - echo "wordpress_helper->esc_attr($val)} /> Yes
"; + echo " Yes
'; echo 'Using XML-RPC bypasses two-factor authentication and makes your website less secure. We recommend only using the WordPress web interface for managing your WordPress website.'; } function duoup_xmlrpc_validate( $option ) { - $option = $this->wordpress_helper->sanitize_text_field( $option ); - if ( $option === 'off' ) { + $option = sanitize_text_field( $option ); + if ( 'off' === $option ) { return $option; } return 'on'; } - function duo_add_link( $links, $file ) { - $settings_link = '' . $this->wordpress_helper->translate( 'Settings', 'duo_universal_wordpress' ) . ''; + function duo_add_link( $links ) { + $settings_link = '' . \__( 'Settings', 'duo_universal_wordpress' ) . ''; array_unshift( $links, $settings_link ); return $links; } function duo_add_page() { - if ( ! $this->wordpress_helper->is_multisite() ) { - $this->wordpress_helper->add_options_page( 'Duo Universal Two-Factor', 'Duo Universal Two-Factor', 'manage_options', 'duo_universal_wordpress', array( $this, 'duo_settings_page' ) ); + if ( ! is_multisite() ) { + add_options_page( 'Duo Universal Two-Factor', 'Duo Universal Two-Factor', 'manage_options', 'duo_universal_wordpress', array( $this, 'duo_settings_page' ) ); } } @@ -204,18 +214,18 @@ function duo_add_site_option( $option, $value = '' ) { // Add multisite option only if it doesn't exist already // With WordPress versions < 3.3, calling add_site_option will override old values if ( $this->duo_utils->duo_get_option( $option ) === false ) { - $this->wordpress_helper->add_site_option( $option, $value ); + \add_site_option( $option, $value ); } } function duo_admin_init() { - if ( $this->wordpress_helper->is_multisite() ) { + if ( is_multisite() ) { $wp_roles = $this->duo_utils->duo_get_roles(); $roles = $wp_roles->get_names(); $allroles = array(); foreach ( $roles as $key => $role ) { - $allroles[ $this->wordpress_helper->before_last_bar( $key ) ] = $this->wordpress_helper->before_last_bar( $role ); + $allroles[ \before_last_bar( $key ) ] = \before_last_bar( $role ); } $this->duo_add_site_option( 'duoup_client_id', '' ); @@ -225,20 +235,20 @@ function duo_admin_init() { $this->duo_add_site_option( 'duoup_roles', $allroles ); $this->duo_add_site_option( 'duoup_xmlrpc', 'off' ); } else { - $this->wordpress_helper->add_settings_section( 'duo_universal_settings', 'Main Settings', array( $this, 'duo_settings_text' ), 'duo_universal_settings' ); - $this->wordpress_helper->add_settings_field( 'duoup_client_id', 'Client ID', array( $this, 'duo_settings_client_id' ), 'duo_universal_settings', 'duo_universal_settings' ); - $this->wordpress_helper->add_settings_field( 'duoup_client_secret', 'Client Secret', array( $this, 'duo_settings_client_secret' ), 'duo_universal_settings', 'duo_universal_settings' ); - $this->wordpress_helper->add_settings_field( 'duoup_api_host', 'API hostname', array( $this, 'duo_settings_host' ), 'duo_universal_settings', 'duo_universal_settings' ); - $this->wordpress_helper->add_settings_field( 'duoup_failmode', 'Failmode', array( $this, 'duo_settings_failmode' ), 'duo_universal_settings', 'duo_universal_settings' ); - $this->wordpress_helper->add_settings_field( 'duoup_roles', 'Enable for roles:', array( $this, 'duo_settings_roles' ), 'duo_universal_settings', 'duo_universal_settings' ); - $this->wordpress_helper->add_settings_field( 'duoup_xmlrpc', 'Disable XML-RPC (recommended)', array( $this, 'duo_settings_xmlrpc' ), 'duo_universal_settings', 'duo_universal_settings' ); - - $this->wordpress_helper->register_setting( 'duo_universal_settings', 'duoup_client_id', array( $this, 'duoup_client_id_validate' ) ); - $this->wordpress_helper->register_setting( 'duo_universal_settings', 'duoup_client_secret', array( $this, 'duoup_client_secret_validate' ) ); - $this->wordpress_helper->register_setting( 'duo_universal_settings', 'duoup_api_host', array( $this, 'duoup_api_host_validate' ) ); - $this->wordpress_helper->register_setting( 'duo_universal_settings', 'duoup_failmode', array( $this, 'duoup_failmode_validate' ) ); - $this->wordpress_helper->register_setting( 'duo_universal_settings', 'duoup_roles', array( $this, 'duoup_roles_validate' ) ); - $this->wordpress_helper->register_setting( 'duo_universal_settings', 'duoup_xmlrpc', array( $this, 'duoup_xmlrpc_validate' ) ); + \add_settings_section( 'duo_universal_settings', 'Main Settings', array( $this, 'duo_settings_text' ), 'duo_universal_settings' ); + \add_settings_field( 'duoup_client_id', 'Client ID', array( $this, 'duo_settings_client_id' ), 'duo_universal_settings', 'duo_universal_settings' ); + \add_settings_field( 'duoup_client_secret', 'Client Secret', array( $this, 'duo_settings_client_secret' ), 'duo_universal_settings', 'duo_universal_settings' ); + \add_settings_field( 'duoup_api_host', 'API hostname', array( $this, 'duo_settings_host' ), 'duo_universal_settings', 'duo_universal_settings' ); + \add_settings_field( 'duoup_failmode', 'Failmode', array( $this, 'duo_settings_failmode' ), 'duo_universal_settings', 'duo_universal_settings' ); + \add_settings_field( 'duoup_roles', 'Enable for roles:', array( $this, 'duo_settings_roles' ), 'duo_universal_settings', 'duo_universal_settings' ); + \add_settings_field( 'duoup_xmlrpc', 'Disable XML-RPC (recommended)', array( $this, 'duo_settings_xmlrpc' ), 'duo_universal_settings', 'duo_universal_settings' ); + + \register_setting( 'duo_universal_settings', 'duoup_client_id', array( $this, 'duoup_client_id_validate' ) ); + \register_setting( 'duo_universal_settings', 'duoup_client_secret', array( $this, 'duoup_client_secret_validate' ) ); + \register_setting( 'duo_universal_settings', 'duoup_api_host', array( $this, 'duoup_api_host_validate' ) ); + \register_setting( 'duo_universal_settings', 'duoup_failmode', array( $this, 'duoup_failmode_validate' ) ); + \register_setting( 'duo_universal_settings', 'duoup_roles', array( $this, 'duoup_roles_validate' ) ); + \register_setting( 'duo_universal_settings', 'duoup_xmlrpc', array( $this, 'duoup_xmlrpc_validate' ) ); } } @@ -261,39 +271,40 @@ function duo_mu_options() { function duo_update_mu_options() { if ( isset( $_POST['duoup_client_id'] ) ) { - $client_id = $this->duoup_client_id_validate( $_POST['duoup_client_id'] ); - $result = $this->wordpress_helper->update_site_option( 'duoup_client_id', $client_id ); + $client_id = $this->duoup_client_id_validate( sanitize_text_field( \wp_unslash( $_POST['duoup_client_id'] ) ) ); + $result = \update_site_option( 'duoup_client_id', $client_id ); } if ( isset( $_POST['duoup_client_secret'] ) ) { - $client_secret = $this->duoup_client_secret_validate( $_POST['duoup_client_secret'] ); - $result = $this->wordpress_helper->update_site_option( 'duoup_client_secret', $client_secret ); + $client_secret = $this->duoup_client_secret_validate( sanitize_text_field( \wp_unslash( $_POST['duoup_client_secret'] ) ) ); + $result = \update_site_option( 'duoup_client_secret', $client_secret ); } if ( isset( $_POST['duoup_api_host'] ) ) { - $host = $this->duoup_api_host_validate( $_POST['duoup_api_host'] ); - $result = $this->wordpress_helper->update_site_option( 'duoup_api_host', $host ); + $host = $this->duoup_api_host_validate( sanitize_text_field( \wp_unslash( $_POST['duoup_api_host'] ) ) ); + $result = \update_site_option( 'duoup_api_host', $host ); } if ( isset( $_POST['duoup_failmode'] ) ) { - $failmode = $this->duoup_failmode_validate( $_POST['duoup_failmode'] ); - $result = $this->wordpress_helper->update_site_option( 'duoup_failmode', $failmode ); + $failmode = $this->duoup_failmode_validate( sanitize_text_field( \wp_unslash( $_POST['duoup_failmode'] ) ) ); + $result = \update_site_option( 'duoup_failmode', $failmode ); } else { - $result = $this->wordpress_helper->update_site_option( 'duoup_failmode', 'open' ); + $result = \update_site_option( 'duoup_failmode', 'open' ); } if ( isset( $_POST['duoup_roles'] ) ) { - $roles = $this->duoup_roles_validate( $_POST['duoup_roles'] ); - $result = $this->wordpress_helper->update_site_option( 'duoup_roles', $roles ); + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $roles = $this->duoup_roles_validate( \wp_unslash( $_POST['duoup_roles'] ) ); + $result = \update_site_option( 'duoup_roles', $roles ); } else { - $result = $this->wordpress_helper->update_site_option( 'duoup_roles', array() ); + $result = \update_site_option( 'duoup_roles', array() ); } if ( isset( $_POST['duoup_xmlrpc'] ) ) { - $xmlrpc = $this->duoup_xmlrpc_validate( $_POST['duoup_xmlrpc'] ); - $result = $this->wordpress_helper->update_site_option( 'duoup_xmlrpc', $xmlrpc ); + $xmlrpc = $this->duoup_xmlrpc_validate( sanitize_text_field( \wp_unslash( $_POST['duoup_xmlrpc'] ) ) ); + $result = \update_site_option( 'duoup_xmlrpc', $xmlrpc ); } else { - $result = $this->wordpress_helper->update_site_option( 'duoup_xmlrpc', 'on' ); + $result = \update_site_option( 'duoup_xmlrpc', 'on' ); } } } diff --git a/class-duouniversal-utilities.php b/class-duouniversal-utilities.php index 51909e2..e97a958 100644 --- a/class-duouniversal-utilities.php +++ b/class-duouniversal-utilities.php @@ -1,14 +1,20 @@ wordpress_helper = $wordpress_helper; - } - function xmlrpc_enabled() { return defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST; } @@ -16,8 +22,8 @@ function xmlrpc_enabled() { function duo_get_roles() { global $wp_roles; // $wp_roles may not be initially set if WordPress < 3.3 - $wp_roles = isset( $wp_roles ) ? $wp_roles : $this->wordpress_helper->WP_Roles(); - return $wp_roles; + $roles = isset( $wp_roles ) ? $wp_roles : \WP_Roles(); + return $roles; } function duo_auth_enabled() { @@ -26,7 +32,7 @@ function duo_auth_enabled() { return false; // allows the XML-RPC protocol for remote publishing } - if ( $this->duo_get_option( 'duoup_client_id', '' ) === '' || $this->duo_get_option( 'duoup_client_secret', '' ) == '' + if ( $this->duo_get_option( 'duoup_client_id', '' ) === '' || $this->duo_get_option( 'duoup_client_secret', '' ) === '' || $this->duo_get_option( 'duoup_api_host', '' ) === '' ) { return false; @@ -49,7 +55,7 @@ function duo_role_require_mfa( $user ) { * Don't use get_user_by() */ if ( ! isset( $user->roles ) ) { - $user = $this->wordpress_helper->WP_User( 0, $user->user_login ); + $user = $this->new_WP_User( 0, $user->user_login ); } /* @@ -77,24 +83,31 @@ function duo_get_uri() { // paths (for which protocols are not required/enforced), and REQUEST_URI // always includes the leading slash in the URI path. if ( ! isset( $_SERVER['REQUEST_URI'] ) - || ( ! empty( $_SERVER['QUERY_STRING'] ) && ! strpos( $this->wordpress_helper->sanitize_url( $_SERVER['REQUEST_URI'] ), '?', 0 ) ) + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + || ( ! empty( $_SERVER['QUERY_STRING'] ) && ! strpos( \sanitize_url( $_SERVER['REQUEST_URI'] ), '?', 0 ) ) ) { - $current_uri = $this->wordpress_helper->sanitize_url( substr( $_SERVER['PHP_SELF'], 1 ) ); + if ( ! isset( $_SERVER['PHP_SELF'] ) ) { + throw new Exception( 'Could not determine request URI' ); + } + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $current_uri = isset( $_SERVER['PHP_SELF'] ) ? substr( \sanitize_url( $_SERVER['PHP_SELF'] ), 1 ) : null; if ( isset( $_SERVER['QUERY_STRING'] ) ) { - $current_uri = $this->wordpress_helper->sanitize_url( $current_uri . '?' . $_SERVER['QUERY_STRING'] ); + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + $current_uri = \sanitize_url( $current_uri . '?' . $_SERVER['QUERY_STRING'] ); } return $current_uri; } else { - return $this->wordpress_helper->sanitize_url( $_SERVER['REQUEST_URI'] ); + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash + return \sanitize_url( $_SERVER['REQUEST_URI'] ); } } - function duo_get_option( $key, $default = '' ) { - if ( $this->wordpress_helper->is_multisite() ) { - return $this->wordpress_helper->get_site_option( $key, $default ); + function duo_get_option( $key, $default_value = '' ) { + if ( \is_multisite() ) { + return \get_site_option( $key, $default_value ); } else { - return $this->wordpress_helper->get_option( $key, $default ); + return \get_option( $key, $default_value ); } } @@ -104,4 +117,12 @@ function duo_debug_log( $message ) { error_log( 'Duo debug: ' . $message ); } } + + function new_WP_User( $id, $name = '', $site_id = '' ) { + return new \WP_User( $id, $name, $site_id ); + } + + function new_WP_Error( $code, $message = '', $data = '' ) { + return new \WP_Error( $code, $message, $data ); + } } diff --git a/class-duouniversal-wordpresshelper.php b/class-duouniversal-wordpresshelper.php deleted file mode 100644 index d57048c..0000000 --- a/class-duouniversal-wordpresshelper.php +++ /dev/null @@ -1,158 +0,0 @@ -duo_client = $duo_client; - $this->duo_utils = $duo_utils; - $this->wordpress_helper = $duo_utils->wordpress_helper; + $this->duo_client = $duo_client; + $this->duo_utils = $duo_utils; } - // Sets a user's auth state - // user: username of the user to update - // status: whether or not an authentication is in progress or is completed ("in-progress" or "authenticated") + /** + * Sets a user's auth state + * user: username of the user to update + * status: whether or not an authentication is in progress or is completed ("in-progress" or "authenticated") + **/ function update_user_auth_status( $user, $status, $redirect_url = '', $oidc_state = null ) { - $this->wordpress_helper->set_transient( 'duo_auth_' . $user . '_status', $status, DUO_TRANSIENT_EXPIRATION ); + \set_transient( 'duo_auth_' . $user . '_status', $status, DUO_TRANSIENT_EXPIRATION ); if ( $redirect_url ) { - $this->wordpress_helper->set_transient( 'duo_auth_' . $user . '_redirect_url', $redirect_url, DUO_TRANSIENT_EXPIRATION ); + \set_transient( 'duo_auth_' . $user . '_redirect_url', $redirect_url, DUO_TRANSIENT_EXPIRATION ); } if ( $oidc_state ) { - // we need to track the state in two places so we can clean up later - $this->wordpress_helper->set_transient( 'duo_auth_' . $user . '_oidc_state', $oidc_state, DUO_TRANSIENT_EXPIRATION ); - $this->wordpress_helper->set_transient( "duo_auth_state_$oidc_state", $user, DUO_TRANSIENT_EXPIRATION ); + // we need to track the state in two places so we can clean up later. + \set_transient( 'duo_auth_' . $user . '_oidc_state', $oidc_state, DUO_TRANSIENT_EXPIRATION ); + \set_transient( "duo_auth_state_$oidc_state", $user, DUO_TRANSIENT_EXPIRATION ); } } @@ -42,7 +53,7 @@ function duo_debug_log( $str ) { } function get_user_auth_status( $user ) { - return $this->wordpress_helper->get_transient( 'duo_auth_' . $user . '_status' ); + return \get_transient( 'duo_auth_' . $user . '_status' ); } function duo_verify_auth_status( $user ) { @@ -50,30 +61,30 @@ function duo_verify_auth_status( $user ) { } function get_username_from_oidc_state( $oidc_state ) { - return $this->wordpress_helper->get_transient( "duo_auth_state_$oidc_state" ); + return \get_transient( "duo_auth_state_$oidc_state" ); } function get_redirect_url( $user ) { - return $this->wordpress_helper->get_transient( 'duo_auth_' . $user . '_redirect_url' ); + return \get_transient( 'duo_auth_' . $user . '_redirect_url' ); } function clear_user_auth( $user ) { $username = $user->user_login; try { - $oidc_state = $this->wordpress_helper->get_transient( 'duo_auth_' . $username . '_oidc_state' ); + $oidc_state = \get_transient( 'duo_auth_' . $username . '_oidc_state' ); - $this->wordpress_helper->delete_transient( 'duo_auth_' . $username . '_status' ); - $this->wordpress_helper->delete_transient( 'duo_auth_' . $username . '_oidc_state' ); - $this->wordpress_helper->delete_transient( "duo_auth_state_$oidc_state" ); - $this->wordpress_helper->delete_transient( 'duo_auth_' . $username . '_redirect_url' ); + \delete_transient( 'duo_auth_' . $username . '_status' ); + \delete_transient( 'duo_auth_' . $username . '_oidc_state' ); + \delete_transient( "duo_auth_state_$oidc_state" ); + \delete_transient( 'duo_auth_' . $username . '_redirect_url' ); } catch ( Exception $e ) { - // there's not much we can do but we shouldn't fail the logout because of this + // there's not much we can do but we shouldn't fail the logout because of this. $this->duo_debug_log( $e->getMessage() ); } } function clear_current_user_auth() { - $user = $this->wordpress_helper->wp_get_current_user(); + $user = \wp_get_current_user(); $this->clear_user_auth( $user ); } @@ -82,10 +93,20 @@ function get_page_url() { // the script was queried through HTTPS. However, IIS will set the // value to 'off' HTTPS was not used, so we have to check that special // case. - $https_used = ( ! empty( $_SERVER['HTTPS'] ) && strtolower( $this->wordpress_helper->sanitize_text_field( $_SERVER['HTTPS'] ) ) !== 'off' ); - $port = absint( $_SERVER['SERVER_PORT'] ); - $protocol = ( $https_used || $port === 443 ) ? 'https://' : 'http://'; - return $this->wordpress_helper->sanitize_url( $protocol . $_SERVER['HTTP_HOST'] . $this->duo_utils->duo_get_uri(), array( 'http', 'https' ) ); + $https_used = ( ! empty( $_SERVER['HTTPS'] ) && strtolower( \sanitize_text_field( wp_unslash( $_SERVER['HTTPS'] ) ) ) !== 'off' ); + + if ( ! isset( $_SERVER['SERVER_PORT'] ) ) { + throw new Exception( 'Could not determine server port' ); + } + + $port = isset( $_SERVER['SERVER_PORT'] ) ? absint( $_SERVER['SERVER_PORT'] ) : null; + $protocol = ( $https_used || 443 === $port ) ? 'https://' : 'http://'; + + if ( ! isset( $_SERVER['HTTP_HOST'] ) ) { + throw new Exception( 'Could not determine host' ); + } + $host = ! empty( $_SERVER['HTTP_HOST'] ) ? \sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : null; + return \sanitize_url( $protocol . $host . $this->duo_utils->duo_get_uri(), array( 'http', 'https' ) ); } function exit() { @@ -104,14 +125,14 @@ function duo_start_second_factor( $user ) { $this->duo_client->redirect_url = $redirect_url; $this->update_user_auth_status( $user->user_login, 'in-progress', $redirect_url, $oidc_state ); - $this->wordpress_helper->wp_logout(); + \wp_logout(); $prompt_uri = $this->duo_client->createAuthUrl( $user->user_login, $oidc_state ); - $this->wordpress_helper->wp_redirect( $prompt_uri ); + \wp_redirect( $prompt_uri ); $this->exit(); } function duo_authenticate_user( $user = '', $username = '', $password = '' ) { - // play nicely with other plugins if they have higher priority than us + // play nicely with other plugins if they have higher priority than us. // if ( is_a( $user, 'WP_User' ) ) { // return $user; // } @@ -122,70 +143,71 @@ function duo_authenticate_user( $user = '', $username = '', $password = '' ) { } if ( isset( $_GET['duo_code'] ) ) { - // secondary auth + // doing secondary auth. if ( isset( $_GET['error'] ) ) { - $error_msg = $this->wordpress_helper->sanitize_text_field( $_GET['error'] ) . ':' . $this->wordpress_helper->sanitize_text_field( $_GET['error_description'] ); - $error = $this->wordpress_helper->WP_Error( + $error_msg = \sanitize_text_field( wp_unslash( $_GET['error'] ) ); + if ( isset( $_GET['error_description'] ) ) { + $error_msg .= ': ' . \sanitize_text_field( wp_unslash( $_GET['error_description'] ) ); + } + $error = $this->duo_utils->new_WP_Error( 'Duo authentication failed', - $this->wordpress_helper->translate( "ERROR: $error_msg" ) + // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText + \__( 'ERROR: ' . $error_msg ) ); - $this->duo_debug_log( $error_msg ); + $this->duo_debug_log( $error->get_error_message() ); return $error; } if ( ! isset( $_GET['state'] ) ) { - $error_msg = 'Missing state'; - $error = $this->wordpress_helper->WP_Error( + $error = $this->duo_utils->new_WP_Error( 'Duo authentication failed', - $this->wordpress_helper->translate( "ERROR: $error_msg" ) + \__( 'ERROR: Missing state' ) ); - $this->duo_debug_log( $error_msg ); + $this->duo_debug_log( $error->get_error_message() ); return $error; } $this->duo_debug_log( 'Doing secondary auth' ); - // Get authorization token to trade for 2FA - $code = $this->wordpress_helper->sanitize_text_field( $_GET['duo_code'] ); + // Get authorization token to trade for 2FA. + $code = \sanitize_text_field( wp_unslash( $_GET['duo_code'] ) ); - // Get state to verify consistency and originality - $state = $this->wordpress_helper->sanitize_text_field( $_GET['state'] ); + // Get state to verify consistency and originality. + $state = \sanitize_text_field( wp_unslash( $_GET['state'] ) ); - // Retrieve the previously stored state and username from the session + // Retrieve the previously stored state and username from the session. $associated_user = $this->get_username_from_oidc_state( $state ); if ( empty( $associated_user ) ) { - $error_msg = 'No saved state please login again'; - $error = $this->wordpress_helper->WP_Error( + $error = $this->duo_utils->new_WP_Error( 'Duo authentication failed', - $this->wordpress_helper->translate( "ERROR: $error_msg" ) + \__( 'ERROR: No saved state please login again' ) ); - $this->duo_debug_log( $error_msg ); + $this->duo_debug_log( $error->get_error_message() ); return $error; } try { - // Update redirect URL to be one associated with initial authentication + // Update redirect URL to be one associated with initial authentication. $this->duo_client->redirect_url = $this->get_redirect_url( $associated_user ); $decoded_token = $this->duo_client->exchangeAuthorizationCodeFor2FAResult( $code, $associated_user ); } catch ( Duo\DuoUniversal\DuoException $e ) { $this->duo_debug_log( $e->getMessage() ); - $error_msg = 'Error decoding Duo result. Confirm device clock is correct.'; - $error = $this->wordpress_helper->WP_Error( + $error = $this->duo_utils->new_WP_Error( 'Duo authentication failed', - $this->wordpress_helper->translate( "ERROR: $error_msg" ) + \__( 'ERROR: Error decoding Duo result. Confirm device clock is correct.' ) ); - $this->duo_debug_log( $error_msg ); + $this->duo_debug_log( $error->get_error_message() ); return $error; } $this->duo_debug_log( "Completed secondary auth for $associated_user" ); $this->update_user_auth_status( $associated_user, 'authenticated' ); - $user = $this->wordpress_helper->WP_User( 0, $associated_user ); + $user = $this->duo_utils->new_WP_user( 0, $associated_user ); return $user; } // if ( strlen( $username ) > 0 ) { -// // primary auth -// // Don't use get_user_by(). It doesn't return a WP_User object if WordPress version < 3.3 -// $user = $this->wordpress_helper->WP_User( 0, $username ); +// // primary auth. +// // Don't use get_user_by(). It doesn't return a WP_User object if WordPress version < 3.3. +// $user = $this->duo_utils->new_WP_User( 0, $username ); // if ( ! $user ) { // $this->error_log( "Failed to retrieve WP user $username" ); // return; @@ -197,15 +219,16 @@ function duo_authenticate_user( $user = '', $username = '', $password = '' ) { // } // // $this->duo_debug_log( 'Doing primary authentication' ); -// $this->wordpress_helper->remove_action( 'authenticate', 'wp_authenticate_username_password', 20 ); -// $user = $this->wordpress_helper->wp_authenticate_username_password( null, $username, $password ); +// \remove_action( 'authenticate', 'wp_authenticate_username_password', 20 ); +// +// $user = \wp_authenticate_username_password( null, $username, $password ); // if ( ! is_a( $user, 'WP_User' ) ) { -// // maybe we got an email -// $user = $this->wordpress_helper->wp_authenticate_email_password( null, $username, $password ); +// // maybe we got an email? +// $user = \wp_authenticate_email_password( null, $username, $password ); // } if ( ! is_a( $user, 'WP_User' ) ) { - // on error, return said error (and skip the remaining plugin chain) + // on error, return said error (and skip the remaining plugin chain). return $user; } else { $this->duo_debug_log( "Primary auth succeeded, starting second factor for $username" ); @@ -215,17 +238,16 @@ function duo_authenticate_user( $user = '', $username = '', $password = '' ) { } catch ( Duo\DuoUniversal\DuoException $e ) { $this->duo_debug_log( $e->getMessage() ); if ( $this->duo_utils->duo_get_option( 'duoup_failmode' ) === 'open' ) { - // If we're failing open, errors in 2FA still allow for success + // If we're failing open, errors in 2FA still allow for success. $this->duo_debug_log( "Login 'Successful', but 2FA Not Performed. Confirm Duo client/secret/host values are correct" ); $this->update_user_auth_status( $user->user_login, 'authenticated' ); return $user; } else { - $error_msg = '2FA Unavailable. Confirm Duo client/secret/host values are correct'; - $error = $this->wordpress_helper->WP_Error( - 'Duo authentication_failed', - $this->wordpress_helper->translate( "Error: $error_msg" ) + $error = $this->duo_utils->new_WP_Error( + 'Duo authentication failed', + \__( 'Error: 2FA Unavailable. Confirm Duo client/secret/host values are correct' ) ); - $this->duo_debug_log( $error_msg ); + $this->duo_debug_log( $error->get_error_message() ); $this->clear_user_auth( $user ); return $error; } @@ -235,14 +257,14 @@ function duo_authenticate_user( $user = '', $username = '', $password = '' ) { // $this->duo_debug_log( 'Starting primary authentication' ); } + /** + * Verify the user is authenticated with Duo. Start 2FA otherwise + */ function duo_verify_auth() { - /* - Verify the user is authenticated with Duo. Start 2FA otherwise - */ if ( ! $this->duo_utils->duo_auth_enabled() ) { // XXX do we still need this skipping logic? - if ( $this->wordpress_helper->is_multisite() ) { - $site_info = $this->wordpress_helper->get_current_site(); + if ( \is_multisite() ) { + $site_info = \get_current_site(); $this->duo_debug_log( 'Duo not enabled on ' . $site_info->site_name ); } else { $this->duo_debug_log( 'Duo not enabled, skip auth check.' ); @@ -250,8 +272,8 @@ function duo_verify_auth() { return; } - if ( $this->wordpress_helper->is_user_logged_in() ) { - $user = $this->wordpress_helper->wp_get_current_user(); + if ( \is_user_logged_in() ) { + $user = \wp_get_current_user(); $this->duo_debug_log( "Verifying auth state for user: $user->user_login" ); if ( $this->duo_utils->duo_role_require_mfa( $user ) && ! $this->duo_verify_auth_status( $user->user_login ) ) { $this->duo_debug_log( "User not authenticated with Duo. Starting second factor for: $user->user_login" ); diff --git a/composer.json b/composer.json index a0c3c1f..5114bc5 100644 --- a/composer.json +++ b/composer.json @@ -15,12 +15,16 @@ "duosecurity/duo_universal_php": ">=1.0.1" }, "require-dev": { - "phpunit/phpunit": "^8.0", - "squizlabs/php_codesniffer": "3.*" + "phpunit/phpunit": "^9.0", + "squizlabs/php_codesniffer": "3.*", + "wp-coding-standards/wpcs": "^3.0", + "10up/wp_mock": "^1.0", + "mockery/mockery": "^1.6" }, "config": { "allow-plugins": { - "composer/installers": true + "composer/installers": true, + "dealerdirect/phpcodesniffer-composer-installer": true } } } diff --git a/composer.lock b/composer.lock index da1c2e7..92c8918 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "701fdf29593acfbbc3262a1609c1c122", + "content-hash": "991a25ed1815de443edb0e8637c74468", "packages": [ { "name": "composer/installers", @@ -260,32 +260,212 @@ } ], "packages-dev": [ + { + "name": "10up/wp_mock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/10up/wp_mock.git", + "reference": "de812e445e17832703571081aa40f39140497117" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/10up/wp_mock/zipball/de812e445e17832703571081aa40f39140497117", + "reference": "de812e445e17832703571081aa40f39140497117", + "shasum": "" + }, + "require": { + "antecedent/patchwork": "^2.1", + "mockery/mockery": "^1.6", + "php": ">=7.4 < 8.3", + "phpunit/phpunit": "^9.6" + }, + "require-dev": { + "behat/behat": "^v3.11.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "friendsofphp/php-cs-fixer": "^3.4", + "php-coveralls/php-coveralls": "^v2.5", + "php-stubs/wordpress-globals": "^0.2", + "php-stubs/wordpress-stubs": "^6.2", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "sebastian/comparator": "^4.0.8", + "sempro/phpunit-pretty-print": "^1.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "WP_Mock\\": "./php/WP_Mock" + }, + "classmap": [ + "php/WP_Mock.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "A mocking library to take the pain out of unit testing for WordPress", + "support": { + "issues": "https://github.com/10up/wp_mock/issues", + "source": "https://github.com/10up/wp_mock/tree/1.0.0" + }, + "time": "2023-07-26T03:03:21+00:00" + }, + { + "name": "antecedent/patchwork", + "version": "2.1.26", + "source": { + "type": "git", + "url": "https://github.com/antecedent/patchwork.git", + "reference": "f2dae0851b2eae4c51969af740fdd0356d7f8f55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antecedent/patchwork/zipball/f2dae0851b2eae4c51969af740fdd0356d7f8f55", + "reference": "f2dae0851b2eae4c51969af740fdd0356d7f8f55", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": ">=4" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignas Rudaitis", + "email": "ignas.rudaitis@gmail.com" + } + ], + "description": "Method redefinition (monkey-patching) functionality for PHP.", + "homepage": "http://patchwork2.org/", + "keywords": [ + "aop", + "aspect", + "interception", + "monkeypatching", + "redefinition", + "runkit", + "testing" + ], + "support": { + "issues": "https://github.com/antecedent/patchwork/issues", + "source": "https://github.com/antecedent/patchwork/tree/2.1.26" + }, + "time": "2023-09-18T08:18:37+00:00" + }, + { + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" + }, + "require-dev": { + "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "franck.nijhof@dealerdirect.com", + "homepage": "http://www.frenck.nl", + "role": "Developer / IT Manager" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://www.dealerdirect.com", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcbf", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" + }, + "time": "2023-01-05T11:28:13+00:00" + }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -312,7 +492,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -328,20 +508,156 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:23:10+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.6", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/b8e0bb7d8c604046539c1115994632c74dcb361e", + "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.10", + "psalm/plugin-phpunit": "^0.18.4", + "symplify/easy-coding-standard": "^11.5.0", + "vimeo/psalm": "^4.30" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2023-08-09T00:03:52+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -379,7 +695,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -387,7 +703,63 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.17.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + }, + "time": "2023-08-13T19:53:39+00:00" }, { "name": "phar-io/manifest", @@ -500,42 +872,182 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpcsstandards/phpcsextra", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", + "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/746c3190ba8eb2f212087c947ba75f4f5b9a58d5", + "reference": "746c3190ba8eb2f212087c947ba75f4f5b9a58d5", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "phpcsstandards/phpcsutils": "^1.0.8", + "squizlabs/php_codesniffer": "^3.7.1" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "phpcsstandards/phpcsdevtools": "^1.2.1", + "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" + } + ], + "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", + "source": "https://github.com/PHPCSStandards/PHPCSExtra" + }, + "time": "2023-09-20T22:06:18+00:00" + }, + { + "name": "phpcsstandards/phpcsutils", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", + "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/69465cab9d12454e5e7767b9041af0cd8cd13be7", + "reference": "69465cab9d12454e5e7767b9041af0cd8cd13be7", + "shasum": "" + }, + "require": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", + "php": ">=5.4", + "squizlabs/php_codesniffer": "^3.7.1 || 4.0.x-dev@dev" + }, + "require-dev": { + "ext-filter": "*", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcsstandards/phpcsdevcs": "^1.1.6", + "yoast/phpunit-polyfills": "^1.0.5 || ^2.0.0" + }, + "type": "phpcodesniffer-standard", + "extra": { + "branch-alias": { + "dev-stable": "1.x-dev", + "dev-develop": "1.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPCSUtils/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" + } + ], + "description": "A suite of utility functions for use with PHP_CodeSniffer", + "homepage": "https://phpcsutils.com/", + "keywords": [ + "PHP_CodeSniffer", + "phpcbf", + "phpcodesniffer-standard", + "phpcs", + "phpcs3", + "standards", + "static analysis", + "tokens", + "utility" + ], + "support": { + "docs": "https://phpcsutils.com/", + "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", + "source": "https://github.com/PHPCSStandards/PHPCSUtils" + }, + "time": "2023-07-16T21:39:41+00:00" + }, { "name": "phpunit/php-code-coverage", - "version": "7.0.15", + "version": "9.2.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "819f92bba8b001d4363065928088de22f25a3a48" + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/819f92bba8b001d4363065928088de22f25a3a48", - "reference": "819f92bba8b001d4363065928088de22f25a3a48", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-xmlwriter": "*", - "php": ">=7.2", - "phpunit/php-file-iterator": "^2.0.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.1.3 || ^4.0", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.2.2", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1.3" + "nikic/php-parser": "^4.15", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^8.2.2" + "phpunit/phpunit": "^9.3" }, "suggest": { - "ext-xdebug": "^2.7.2" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "7.0-dev" + "dev-master": "9.2-dev" } }, "autoload": { @@ -563,7 +1075,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.15" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" }, "funding": [ { @@ -571,32 +1084,32 @@ "type": "github" } ], - "time": "2021-07-26T12:20:09+00:00" + "time": "2023-09-19T04:57:46+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "2.0.5", + "version": "3.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5" + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", - "reference": "42c5ba5220e6904cbfe8b1a1bda7c0cfdc8c12f5", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -623,7 +1136,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.5" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" }, "funding": [ { @@ -631,26 +1144,97 @@ "type": "github" } ], - "time": "2021-12-02T12:42:26+00:00" + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" }, { "name": "phpunit/php-text-template", - "version": "1.2.1", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, "autoload": { "classmap": [ "src/" @@ -674,37 +1258,135 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" }, - "time": "2015-06-21T13:50:34+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" }, { - "name": "phpunit/php-timer", - "version": "2.1.3", + "name": "phpunit/phpunit", + "version": "9.6.13", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", - "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be", + "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be", "shasum": "" }, "require": { - "php": ">=7.1" + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" }, - "require-dev": { - "phpunit/phpunit": "^8.5" + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "9.6-dev" } }, "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], "classmap": [ "src/" ] @@ -720,48 +1402,58 @@ "role": "lead" } ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "timer" + "phpunit", + "testing", + "xunit" ], "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.3" + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13" }, "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "time": "2020-11-30T08:20:02+00:00" + "time": "2023-09-19T05:39:22+00:00" }, { - "name": "phpunit/php-token-stream", - "version": "4.0.4", + "name": "sebastian/cli-parser", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", - "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": "^7.3 || ^8.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -776,17 +1468,15 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { - "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", - "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" }, "funding": [ { @@ -794,61 +1484,32 @@ "type": "github" } ], - "abandoned": true, - "time": "2020-08-04T08:28:15+00:00" + "time": "2020-09-28T06:08:49+00:00" }, { - "name": "phpunit/phpunit", - "version": "8.5.31", + "name": "sebastian/code-unit", + "version": "1.0.8", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "33c126b09a42de5c99e5e8032b54e8221264a74e" + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33c126b09a42de5c99e5e8032b54e8221264a74e", - "reference": "33c126b09a42de5c99e5e8032b54e8221264a74e", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.0", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.2", - "phpunit/php-code-coverage": "^7.0.12", - "phpunit/php-file-iterator": "^2.0.4", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1.2", - "sebastian/comparator": "^3.0.5", - "sebastian/diff": "^3.0.2", - "sebastian/environment": "^4.2.3", - "sebastian/exporter": "^3.1.5", - "sebastian/global-state": "^3.0.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0.1", - "sebastian/type": "^1.1.3", - "sebastian/version": "^2.0.1" + "php": ">=7.3" }, - "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0.0" + "require-dev": { + "phpunit/phpunit": "^9.3" }, - "bin": [ - "phpunit" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "8.5-dev" + "dev-master": "1.0-dev" } }, "autoload": { @@ -867,57 +1528,44 @@ "role": "lead" } ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.31" + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" }, "funding": [ - { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, { "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" } ], - "time": "2022-10-28T05:57:37+00:00" + "time": "2020-10-26T13:08:54+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -939,7 +1587,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" }, "funding": [ { @@ -947,34 +1595,34 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2020-09-28T05:30:19+00:00" }, { "name": "sebastian/comparator", - "version": "3.0.5", + "version": "4.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770" + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dc7ceb4a24aede938c7af2a9ed1de09609ca770", - "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", "shasum": "" }, "require": { - "php": ">=7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1013,7 +1661,64 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.5" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" }, "funding": [ { @@ -1021,33 +1726,33 @@ "type": "github" } ], - "time": "2022-09-14T12:31:48+00:00" + "time": "2020-10-26T15:52:27+00:00" }, { "name": "sebastian/diff", - "version": "3.0.3", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.5 || ^8.0", - "symfony/process": "^2 || ^3.3 || ^4" + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1079,7 +1784,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -1087,27 +1792,27 @@ "type": "github" } ], - "time": "2020-11-30T07:59:04+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", - "version": "4.2.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", - "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^7.5" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-posix": "*" @@ -1115,7 +1820,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -1142,7 +1847,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/4.2.4" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -1150,34 +1855,34 @@ "type": "github" } ], - "time": "2020-11-30T07:53:42+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", - "version": "3.1.5", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6" + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/73a9676f2833b9a7c36968f9d882589cd75511e6", - "reference": "73a9676f2833b9a7c36968f9d882589cd75511e6", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", "shasum": "" }, "require": { - "php": ">=7.0", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-mbstring": "*", - "phpunit/phpunit": "^8.5" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1212,14 +1917,14 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", + "homepage": "https://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" }, "funding": [ { @@ -1227,30 +1932,30 @@ "type": "github" } ], - "time": "2022-09-14T06:00:17+00:00" + "time": "2022-09-14T06:03:37+00:00" }, { "name": "sebastian/global-state", - "version": "3.0.2", + "version": "5.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921" + "reference": "bde739e7565280bda77be70044ac1047bc007e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/de036ec91d55d2a9e0db2ba975b512cdb1c23921", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34", + "reference": "bde739e7565280bda77be70044ac1047bc007e34", "shasum": "" }, "require": { - "php": ">=7.2", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^9.3" }, "suggest": { "ext-uopz": "*" @@ -1258,7 +1963,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -1283,7 +1988,64 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-02T09:26:13+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" }, "funding": [ { @@ -1291,34 +2053,34 @@ "type": "github" } ], - "time": "2022-02-10T06:55:38+00:00" + "time": "2020-11-28T06:42:11+00:00" }, { "name": "sebastian/object-enumerator", - "version": "3.0.4", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", - "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", "shasum": "" }, "require": { - "php": ">=7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1340,7 +2102,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" }, "funding": [ { @@ -1348,32 +2110,32 @@ "type": "github" } ], - "time": "2020-11-30T07:40:27+00:00" + "time": "2020-10-26T13:12:34+00:00" }, { "name": "sebastian/object-reflector", - "version": "1.1.2", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", - "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1395,7 +2157,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.2" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" }, "funding": [ { @@ -1403,32 +2165,32 @@ "type": "github" } ], - "time": "2020-11-30T07:37:18+00:00" + "time": "2020-10-26T13:14:26+00:00" }, { "name": "sebastian/recursion-context", - "version": "3.0.1", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", - "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^9.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -1455,10 +2217,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -1466,29 +2228,32 @@ "type": "github" } ], - "time": "2020-11-30T07:34:24+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", - "version": "2.0.2", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", - "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1510,7 +2275,7 @@ "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" }, "funding": [ { @@ -1518,32 +2283,32 @@ "type": "github" } ], - "time": "2020-11-30T07:30:19+00:00" + "time": "2020-09-28T06:45:17+00:00" }, { "name": "sebastian/type", - "version": "1.1.4", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", - "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { - "php": ">=7.2" + "php": ">=7.3" }, "require-dev": { - "phpunit/phpunit": "^8.2" + "phpunit/phpunit": "^9.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "3.2-dev" } }, "autoload": { @@ -1566,7 +2331,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/1.1.4" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -1574,29 +2339,29 @@ "type": "github" } ], - "time": "2020-11-30T07:25:11+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + "reference": "c6c1022351a901512170118436c764e473f6de8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", "shasum": "" }, "require": { - "php": ">=5.6" + "php": ">=7.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1619,22 +2384,28 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/master" + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" }, - "time": "2016-10-03T07:35:21+00:00" + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.1", + "version": "3.7.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", "shasum": "" }, "require": { @@ -1670,14 +2441,15 @@ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", - "standards" + "standards", + "static analysis" ], "support": { "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", "source": "https://github.com/squizlabs/PHP_CodeSniffer", "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, - "time": "2022-06-18T07:21:10+00:00" + "time": "2023-02-22T23:07:41+00:00" }, { "name": "theseer/tokenizer", @@ -1728,6 +2500,72 @@ } ], "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", + "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1", + "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "ext-libxml": "*", + "ext-tokenizer": "*", + "ext-xmlreader": "*", + "php": ">=5.4", + "phpcsstandards/phpcsextra": "^1.1.0", + "phpcsstandards/phpcsutils": "^1.0.8", + "squizlabs/php_codesniffer": "^3.7.2" + }, + "require-dev": { + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.0", + "phpcsstandards/phpcsdevtools": "^1.2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-iconv": "For improved results", + "ext-mbstring": "For improved results" + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "static analysis", + "wordpress" + ], + "support": { + "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", + "source": "https://github.com/WordPress/WordPress-Coding-Standards", + "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406", + "type": "custom" + } + ], + "time": "2023-09-14T07:06:09+00:00" } ], "aliases": [], diff --git a/docker-compose.yml b/docker-compose.yml index 6ecc1ec..ce09602 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,8 +2,11 @@ version: '3.3' services: wordpress: - image: duo:wordpress - build: ./ + image: duo:wordpress-${WORDPRESS_VERSION:-latest} + build: + context: ./ + args: + wordpress_version: ${WORDPRESS_VERSION:-latest} restart: always ports: - ${HTTP_PORT:-80}:80 diff --git a/duouniversal-wordpress.php b/duouniversal-wordpress.php new file mode 100644 index 0000000..ee1a3d4 --- /dev/null +++ b/duouniversal-wordpress.php @@ -0,0 +1,84 @@ +duo_auth_enabled() ) { + try { + $duo_client = new Client( + $utils->duo_get_option( 'duoup_client_id' ), + $utils->duo_get_option( 'duoup_client_secret' ), + $utils->duo_get_option( 'duoup_api_host' ), + '', + ); + } catch ( Exception $e ) { + $utils->duo_debug_log( $e->getMessage() ); + $duo_client = null; + } +} else { + $duo_client = null; +} + +$duoup_plugin = new DuoUniversal_WordpressPlugin( + $utils, + $duo_client +); + +$settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings( + $utils +); + +if ( ! \is_multisite() ) { + $plugin_name = plugin_basename( __FILE__ ); + add_filter( 'plugin_action_links_' . $plugin_name, array( $settings, 'duo_add_link' ), 10, 2 ); +} + + +/*-------------XML-RPC Features-----------------*/ + +if ( $duoup_plugin->duo_utils->duo_get_option( 'duoup_xmlrpc', 'off' ) === 'off' ) { + \add_filter( 'xmlrpc_enabled', '__return_false' ); +} + +/*-------------Register WordPress Hooks-------------*/ + +\add_action( 'init', array( $duoup_plugin, 'duo_verify_auth' ), 10 ); + +\add_action( 'clear_auth_cookie', array( $duoup_plugin, 'clear_current_user_auth' ), 10 ); + +\add_filter( 'authenticate', array( $plugin, 'duo_authenticate_user' ), 30, 3 ); + +// add single-site submenu option. +\add_action( 'admin_menu', array( $settings, 'duo_add_page' ) ); +\add_action( 'admin_init', array( $settings, 'duo_admin_init' ) ); + +// Custom fields in multi-site network settings. +\add_action( 'wpmu_options', array( $settings, 'duo_mu_options' ) ); +\add_action( 'update_wpmu_options', array( $settings, 'duo_update_mu_options' ) ); diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..7cda7d0 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1 @@ + diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..df5ff20 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,7 @@ +duo_client = $this->createMock(Duo\DuoUniversal\Client::class); - $this->helper = $this->createMock(Duo\DuoUniversalWordpress\DuoUniversal_WordpressHelper::class); // For filtering and sanitization methods provided by wordpress, // simply return the value passed in for filtering unchanged since we // don't have the wordpress methods in scope - $this->helper->method('apply_filters')->willReturnArgument(1); - $this->helper->method('sanitize_url')->willReturnArgument(0); - $this->helper->method('sanitize_text_field')->willReturnArgument(0); - $this->helper->method('esc_attr')->willReturnArgument(0); + WP_Mock::passthruFunction('sanitize_url'); + WP_Mock::passthruFunction('sanitize_text_field'); $this->duo_utils = $this->createMock(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class); - $this->duo_utils->wordpress_helper = $this->helper; } /** @@ -33,7 +29,7 @@ function testUpdateUserAuthStatus(): void $callback = function ($key, $value, $expiration) use (&$map) { $map[$key] = $value; }; - $this->helper->method('set_transient')->willReturnCallback($callback); + WP_Mock::userFunction('set_transient', ['return' => $callback]); $authentication = new DuoUniversal_WordpressPlugin($this->duo_utils, $this->duo_client); $authentication->update_user_auth_status("user", "test_status", "redirect", "oidc_state"); $this->assertEquals($map["duo_auth_user_status"], "test_status"); @@ -50,9 +46,9 @@ function testClearAuthException(): void { $user = $this->createMock(stdClass::class); $user->user_login = "test user"; - $this->helper->method('delete_transient')->willThrowException(new Exception()); - $this->helper->method('get_transient')->willReturn('test user'); - $this->helper->method('wp_get_current_user')->willReturn($user); + WP_Mock::userFunction('delete_transient', ['return' => function() { throw (new Exception()); } ]); + WP_Mock::userFunction('get_transient', ['return' => 'test user']); + WP_Mock::userFunction('wp_get_current_user', [ 'return' => $user ]); $this->duo_utils->expects($this->once())->method('duo_debug_log'); $authentication = new DuoUniversal_WordpressPlugin($this->duo_utils, $this->duo_client); @@ -75,9 +71,9 @@ function testClearAuthRemovesTransients(): void $delete_callback = function ($key) use (&$map) { unset($map[$key]); }; - $this->helper->method('delete_transient')->willReturnCallback($delete_callback); - $this->helper->method('get_transient')->willReturn('state'); - $this->helper->method('wp_get_current_user')->willReturn($user); + WP_mock::userFunction('delete_transient', ['return' => $delete_callback ]); + WP_Mock::userFunction('get_transient', ['return' => 'state']); + WP_Mock::userFunction('wp_get_current_user', [ 'return' => $user ]); $this->duo_utils->expects($this->never())->method('duo_debug_log'); $authentication = new DuoUniversal_WordpressPlugin($this->duo_utils, $this->duo_client); @@ -98,6 +94,9 @@ function testStartSecondFactorRedirectURL(): void ->onlyMethods(['get_page_url', 'exit']) ->getMock(); $authentication->method('get_page_url')->willReturn('fake url'); + WP_Mock::passthruFunction('wp_redirect'); + WP_Mock::passthruFunction('set_transient'); + WP_Mock::userFunction('wp_logout')->once(); $authentication->duo_start_second_factor($user); @@ -112,15 +111,16 @@ function testPromptRedirect(): void $user = $this->createMock(stdClass::class); $user->user_login = "test user"; $this->duo_client->method('createAuthUrl')->willReturn("prompt url"); - $this->helper->expects($this->once()) - ->method('wp_redirect') - ->with($this->equalTo("prompt url")); + WP_Mock::userFunction('wp_redirect')->with("prompt url")->once(); $authentication = $this->getMockBuilder(DuoUniversal_WordpressPlugin::class) ->setConstructorArgs(array($this->duo_utils, $this->duo_client)) ->onlyMethods(['get_page_url', 'exit']) ->getMock(); + WP_Mock::passthruFunction('set_transient'); + WP_Mock::userFunction('wp_logout')->once(); $authentication->duo_start_second_factor($user); + $this->assertConditionsMet(); } /** @@ -139,7 +139,9 @@ function testStartSecondFactorTransients(): void ->onlyMethods(['get_page_url', 'exit']) ->getMock(); $authentication->method('get_page_url')->willReturn("test url"); - $this->helper->method('set_transient')->willReturnCallback($callback); + WP_Mock::passthruFunction('wp_redirect'); + WP_Mock::userFunction('set_transient', ['return' => $callback]); + WP_Mock::userFunction('wp_logout')->once(); $this->duo_client->method('generateState')->willReturn("test state"); $authentication->duo_start_second_factor($user); @@ -161,9 +163,12 @@ function testStartSecondFactorLogout(): void ->setConstructorArgs(array($this->duo_utils, $this->duo_client)) ->onlyMethods(['get_page_url', 'exit']) ->getMock(); - $this->helper->expects($this->once())->method('wp_logout'); + WP_Mock::userFunction('wp_logout')->once(); + WP_Mock::passthruFunction('set_transient'); + WP_Mock::passthruFunction('wp_redirect'); $authentication->duo_start_second_factor($user); + $this->assertConditionsMet(); } /** @@ -178,6 +183,9 @@ function testStartSecondFactorExit(): void ->onlyMethods(['get_page_url', 'exit']) ->getMock(); $authentication->expects($this->once())->method('exit'); + WP_Mock::passthruFunction('set_transient'); + WP_Mock::passthruFunction('wp_redirect'); + WP_Mock::userFunction('wp_logout')->once(); $authentication->duo_start_second_factor($user); } @@ -203,6 +211,8 @@ function testUserIsNotAString(): void $user = $this->getMockBuilder(stdClass::class) ->setMockClassName('WP_User') ->getMock(); + + $this->duo_utils->method('new_WP_user')->willReturn($user); $user->user_login = "test user"; $result = $authentication->duo_authenticate_user($user); @@ -222,6 +232,7 @@ function testAuthUserAuthNotEnabled(): void $user = $this->getMockBuilder(stdClass::class) ->setMockClassName('WP_User') ->getMock(); + $this->duo_utils->method('new_WP_user')->willReturn($user); $user->user_login = "test user"; $result = $authentication->duo_authenticate_user($user); @@ -241,15 +252,20 @@ function testAuthUserAPIErrorSet(): void ] ) ->getMock(); + $error = $this->getMockBuilder(stdClass::class) + ->setMockClassName('WP_Error') + ->addMethods(["get_error_message"]) + ->getMock(); $this->duo_utils->method('duo_auth_enabled')->willReturn(true); - $this->helper->method('translate')->willReturnArgument(0); - $this->helper->method('WP_Error')->willReturnArgument(1); + $this->duo_utils->method('new_WP_Error')->willReturn($error)->with("Duo authentication failed", "ERROR: test error: test description"); + WP_Mock::passthruFunction('__'); + WP_Mock::passthruFunction('wp_unslash'); $_GET['duo_code'] = "testcode"; $_GET['error'] = "test error"; $_GET['error_description'] = "test description"; $result = $authentication->duo_authenticate_user(); - $this->assertRegExp("/test description/", $result); + $this->assertConditionsMet(); } /** @@ -265,13 +281,18 @@ function testAuthUserStateMissing(): void ] ) ->getMock(); + $error = $this->getMockBuilder(stdClass::class) + ->setMockClassName('WP_Error') + ->addMethods(["get_error_message"]) + ->getMock(); $this->duo_utils->method('duo_auth_enabled')->willReturn(true); - $this->helper->method('translate')->willReturnArgument(0); - $this->helper->method('WP_Error')->willReturnArgument(1); + $this->duo_utils->method('new_WP_Error')->willReturn($error)->with("Duo authentication failed", "ERROR: Missing state"); + WP_Mock::passthruFunction('__'); + WP_Mock::passthruFunction('wp_unslash'); $_GET['duo_code'] = "testcode"; - $result = $authentication->duo_authenticate_user(); - $this->assertRegExp("/Missing state/", $result); + $authentication->duo_authenticate_user(); + $this->assertConditionsMet(); } /** @@ -288,16 +309,21 @@ function testAuthUserUserMissing(): void ] ) ->getMock(); + $error = $this->getMockBuilder(stdClass::class) + ->setMockClassName('WP_Error') + ->addMethods(["get_error_message"]) + ->getMock(); $authentication->method('get_username_from_oidc_state')->willReturn(null); $this->duo_utils->method('duo_auth_enabled')->willReturn(true); - $this->helper->method('translate')->willReturnArgument(0); - $this->helper->method('WP_Error')->willReturnArgument(1); + $this->duo_utils->method('new_WP_Error')->willReturn($error)->with("Duo authentication failed", "ERROR: No saved state please login again"); + WP_Mock::passthruFunction('__'); + WP_Mock::passthruFunction('wp_unslash'); $_GET['duo_code'] = "testcode"; $_GET['state'] = "teststate"; - $result = $authentication->duo_authenticate_user(); + $authentication->duo_authenticate_user(); - $this->assertRegExp("/No saved state/", $result); + $this->assertConditionsMet(); } /** @@ -315,17 +341,22 @@ function testAuthUserExceptionHandling(): void ] ) ->getMock(); - $this->helper->method('translate')->willReturnArgument(0); - $this->helper->method('WP_Error')->willReturnArgument(1); + $error = $this->getMockBuilder(stdClass::class) + ->setMockClassName('WP_Error') + ->addMethods(["get_error_message"]) + ->getMock(); + WP_Mock::passthruFunction('__'); + WP_Mock::passthruFunction('wp_unslash'); $this->duo_utils->method('duo_auth_enabled')->willReturn(true); + $this->duo_utils->method('new_WP_Error')->willReturn($error)->with("Duo authentication failed", "ERROR: Error decoding Duo result. Confirm device clock is correct."); $authentication->method('get_username_from_oidc_state')->willReturn("test user"); $this->duo_client->method('exchangeAuthorizationCodeFor2FAResult')->willThrowException(new Duo\DuoUniversal\DuoException("there was a problem")); $_GET['duo_code'] = "testcode"; $_GET['state'] = "teststate"; - $result = $authentication->duo_authenticate_user(); + $authentication->duo_authenticate_user(); - $this->assertRegExp("/Error decoding Duo result/", $result); + $this->assertConditionsMet(); } /** @@ -337,7 +368,8 @@ function testAuthUserSuccess(): void $callback = function ($key, $value, $expiration) use (&$map) { $map[$key] = $value; }; - $this->helper->method('set_transient')->willReturnCallback($callback); + WP_Mock::userFunction('set_transient', ['return' => $callback]); + WP_Mock::passthruFunction('wp_unslash'); $authentication = $this->getMockBuilder(DuoUniversal_WordpressPlugin::class) ->setConstructorArgs(array($this->duo_utils, $this->duo_client)) ->onlyMethods( @@ -350,7 +382,7 @@ function testAuthUserSuccess(): void ->getMock(); $this->duo_utils->method('duo_auth_enabled')->willReturn(true); $authentication->method('get_username_from_oidc_state')->willReturn("test user"); - $this->helper->method("WP_User")->willReturnArgument(1); + $this->duo_utils->method('new_WP_user')->willReturnArgument(1); $_GET['duo_code'] = "testcode"; $_GET['state'] = "teststate"; @@ -399,7 +431,7 @@ function testAuthUserPrimaryNoUser(): void ->getMock(); $authentication->expects($this->once())->method('error_log')->with($this->equalTo("Failed to retrieve WP user test user")); $this->duo_utils->method('duo_auth_enabled')->willReturn(true); - $this->helper->method('WP_User')->willReturn(null); + $this->duo_utils->method('new_WP_user')->willReturn(null); $result = $authentication->duo_authenticate_user(null, "test user"); } @@ -424,10 +456,10 @@ function testAuthUserPrimaryEmail(): void ->getMock(); $user->user_login = "test user"; $user->roles = []; - $this->helper->method('WP_User')->willReturn($user); - $this->helper->method('wp_authenticate_username_password')->willReturn(null); - $this->helper->method('wp_authenticate_email_password')->willReturn("EMAIL"); - $this->helper->expects($this->once())->method('wp_authenticate_email_password'); + $this->duo_utils->method('new_WP_user')->willReturn($user); + WP_Mock::userFunction('wp_authenticate_username_password', [ 'return' => null ]); + WP_Mock::userFunction('wp_authenticate_email_password', [ 'return' => "EMAIL"])->once(); + WP_Mock::passthruFunction('remove_action'); $result = $authentication->duo_authenticate_user(null, "test user"); @@ -444,7 +476,7 @@ function testAuthUserPrimaryNo2FARole(): void $callback = function ($key, $value, $expiration) use (&$map) { $map[$key] = $value; }; - $this->helper->method('set_transient')->willReturnCallback($callback); + WP_Mock::userFunction('set_transient', ['return' => $callback]); $authentication = $this->getMockBuilder(DuoUniversal_WordpressPlugin::class) ->setConstructorArgs(array($this->duo_utils, $this->duo_client)) ->onlyMethods( @@ -460,7 +492,7 @@ function testAuthUserPrimaryNo2FARole(): void ->getMock(); $user->user_login = "test user"; $user->roles = []; - $this->helper->method('WP_User')->willReturn($user); + $this->duo_utils->method('new_WP_user')->willReturn($user); $result = $authentication->duo_authenticate_user(null, "test user"); @@ -488,9 +520,10 @@ function testAuthUserPrimaryErrorValidatingCredentials(): void ->getMock(); $user->user_login = "test user"; $user->roles = []; - $this->helper->method('WP_User')->willReturn($user); - $this->helper->method('wp_authenticate_username_password')->willReturn("ERROR"); - $this->helper->method('wp_authenticate_email_password')->willReturn("ERROR"); + $this->duo_utils->method('new_WP_user')->willReturn($user); + WP_Mock::userFunction('wp_authenticate_username_password', [ 'return' => "ERROR" ]); + WP_Mock::userFunction('wp_authenticate_email_password', [ 'return' => "ERROR"]); + WP_Mock::passthruFunction('remove_action'); $result = $authentication->duo_authenticate_user(null, "test user"); @@ -506,7 +539,7 @@ function testAuthUserPrimaryUpdatesAuthStatus(): void $callback = function ($key, $value, $expiration) use (&$map) { $map[$key] = $value; }; - $this->helper->method('set_transient')->willReturnCallback($callback); + WP_Mock::userFunction('set_transient', ['return' => $callback]); $authentication = $this->getMockBuilder(DuoUniversal_WordpressPlugin::class) ->setConstructorArgs(array($this->duo_utils, $this->duo_client)) ->onlyMethods( @@ -523,8 +556,9 @@ function testAuthUserPrimaryUpdatesAuthStatus(): void ->getMock(); $user->user_login = "test user"; $user->roles = []; - $this->helper->method('WP_User')->willReturn($user); - $this->helper->method('wp_authenticate_username_password')->willReturn($user); + $this->duo_utils->method('new_WP_user')->willReturn($user); + WP_Mock::userFunction('wp_authenticate_username_password', [ 'return' => $user ]); + WP_Mock::passthruFunction('remove_action'); $result = $authentication->duo_authenticate_user(null, "test user"); @@ -540,7 +574,7 @@ function testAuthUserSecondaryExceptionFailmodeOpen(): void $callback = function ($key, $value, $expiration) use (&$map) { $map[$key] = $value; }; - $this->helper->method('set_transient')->willReturnCallback($callback); + WP_Mock::userFunction('set_transient', ['return' => $callback]); $authentication = $this->getMockBuilder(DuoUniversal_WordpressPlugin::class) ->setConstructorArgs(array($this->duo_utils, $this->duo_client)) ->onlyMethods( @@ -557,8 +591,9 @@ function testAuthUserSecondaryExceptionFailmodeOpen(): void ->getMock(); $user->user_login = "test user"; $user->roles = []; - $this->helper->method('WP_User')->willReturn($user); - $this->helper->method('wp_authenticate_username_password')->willReturn($user); + $this->duo_utils->method('new_WP_user')->willReturn($user); + WP_Mock::userFunction('wp_authenticate_username_password', [ 'return' => $user ]); + WP_Mock::passthruFunction('remove_action'); $this->duo_utils->method('duo_get_option')->willReturn('open'); $result = $authentication->duo_authenticate_user(null, "test user"); @@ -577,8 +612,9 @@ function testAuthUserSecondaryExceptionFailmodeClose(): void $delete_callback = function ($key) use (&$map) { unset($map[$key]); }; - $this->helper->method('set_transient')->willReturnCallback($callback); - $this->helper->method('delete_transient')->willReturnCallback($delete_callback); + WP_Mock::userFunction('set_transient', ['return' => $callback]); + WP_Mock::userFunction('delete_transient', ['return' => $delete_callback ]); + WP_Mock::passthruFunction('get_transient'); $authentication = $this->getMockBuilder(DuoUniversal_WordpressPlugin::class) ->setConstructorArgs(array($this->duo_utils, $this->duo_client)) ->onlyMethods( @@ -593,18 +629,23 @@ function testAuthUserSecondaryExceptionFailmodeClose(): void $user = $this->getMockBuilder(stdClass::class) ->setMockClassName('WP_User') ->getMock(); + $error = $this->getMockBuilder(stdClass::class) + ->setMockClassName('WP_Error') + ->addMethods(["get_error_message"]) + ->getMock(); $user->user_login = "test user"; $user->roles = []; - $this->helper->method('WP_User')->willReturn($user); - $this->helper->method('WP_Error')->willReturnArgument(1); - $this->helper->method('translate')->willReturnArgument(0); - $this->helper->method('wp_authenticate_username_password')->willReturn($user); + $this->duo_utils->method('new_WP_user')->willReturn($user); + $this->duo_utils->method('new_WP_Error')->willReturn($error)->with("Duo authentication failed", "Error: 2FA Unavailable. Confirm Duo client/secret/host values are correct"); + WP_Mock::passthruFunction('__'); + WP_Mock::passthruFunction('remove_action'); + WP_Mock::userFunction('wp_authenticate_username_password', [ 'return' => $user ]); $this->duo_utils->method('duo_get_option')->willReturn('closed'); $result = $authentication->duo_authenticate_user(null, "test user"); $this->assertFalse(array_key_exists("duo_auth_test user_status", $map)); - $this->assertRegExp("/2FA Unavailable/", $result); + $this->assertConditionsMet(); } /** @@ -621,6 +662,7 @@ function testVerifyAuthDisabled(): void ) ->getMock(); $this->duo_utils->method('duo_auth_enabled')->willReturn(false); + WP_Mock::userFunction('is_multisite', [ 'return' => false ]); $authentication->expects($this->once()) ->method('duo_debug_log') ->with($this->equalTo("Duo not enabled, skip auth check.")); @@ -628,6 +670,7 @@ function testVerifyAuthDisabled(): void $result = $authentication->duo_verify_auth(); $this->assertEquals($result, null); + $this->assertConditionsMet(); } /** @@ -646,8 +689,8 @@ function testVerifyAuthDisabledMultisite(): void $site = $this->createMock(stdClass::class); $site->site_name = "test site"; $this->duo_utils->method('duo_auth_enabled')->willReturn(false); - $this->helper->method('is_multisite')->willReturn(true); - $this->helper->method('get_current_site')->willReturn($site); + WP_Mock::userFunction('is_multisite', [ 'return' => true ]); + WP_Mock::userFunction('get_current_site', [ 'return' => $site ]); $authentication->expects($this->once()) ->method('duo_debug_log') ->with($this->equalTo("Duo not enabled on test site")); @@ -671,7 +714,7 @@ function testVerifyAuthNotLoggedIn(): void ) ->getMock(); $this->duo_utils->method('duo_auth_enabled')->willReturn(true); - $this->helper->method("is_user_logged_in")->willReturn(false); + WP_Mock::userFunction('is_user_logged_in', [ 'return' => false ]); $authentication->expects($this->never())->method('duo_debug_log'); $result = $authentication->duo_verify_auth(); @@ -695,8 +738,8 @@ function testVerifyAuthRoleNotRequire2FA(): void ) ->getMock(); $this->duo_utils->method('duo_auth_enabled')->willReturn(true); - $this->helper->method("is_user_logged_in")->willReturn(true); - $this->helper->method('wp_get_current_user')->willReturn($user); + WP_Mock::userFunction('is_user_logged_in', [ 'return' => true ]); + WP_Mock::userFunction('wp_get_current_user', [ 'return' => $user ]); $this->duo_utils->method('duo_role_require_mfa')->willReturn(false); $authentication->expects($this->at(1)) ->method('duo_debug_log') @@ -724,8 +767,8 @@ function testVerifyAuthAlreadyVerified(): void ) ->getMock(); $this->duo_utils->method('duo_auth_enabled')->willReturn(true); - $this->helper->method("is_user_logged_in")->willReturn(true); - $this->helper->method('wp_get_current_user')->willReturn($user); + WP_Mock::userFunction('is_user_logged_in', [ 'return' => true ]); + WP_Mock::userFunction('wp_get_current_user', [ 'return' => $user ]); $this->duo_utils->method('duo_role_require_mfa')->willReturn(true); $authentication->method('duo_verify_auth_status')->willReturn(true); $authentication->expects($this->at(2)) @@ -754,8 +797,8 @@ function testVerifyAuthNeeds2FA(): void ) ->getMock(); $this->duo_utils->method('duo_auth_enabled')->willReturn(true); - $this->helper->method("is_user_logged_in")->willReturn(true); - $this->helper->method('wp_get_current_user')->willReturn($user); + WP_Mock::userFunction('is_user_logged_in', [ 'return' => true ]); + WP_Mock::userFunction('wp_get_current_user', [ 'return' => $user ]); $this->duo_utils->method('duo_role_require_mfa')->willReturn(true); $authentication->method('duo_verify_auth_status')->willReturn(false); $authentication->expects($this->once())->method('duo_start_second_factor'); diff --git a/tests/duoUniversalSettingsTest.php b/tests/duoUniversalSettingsTest.php index b0fa339..fc2fab6 100644 --- a/tests/duoUniversalSettingsTest.php +++ b/tests/duoUniversalSettingsTest.php @@ -3,25 +3,24 @@ use Duo\DuoUniversal\DuoException; use Duo\DuoUniversalWordpress; use PHPUnit\Framework\TestCase; +use WP_Mock\Tools\TestCase as WPTestCase; +use WP_Mock; + require_once 'class-duouniversal-settings.php'; -require_once 'class-duouniversal-wordpresshelper.php'; -final class SettingsTest extends TestCase +final class SettingsTest extends WPTestCase { function setUp(): void { $this->duo_client = $this->createMock(Duo\DuoUniversal\Client::class); - $this->helper = $this->createMock(Duo\DuoUniversalWordpress\DuoUniversal_WordpressHelper::class); // For filtering and sanitization methods provided by wordpress, // simply return the value passed in for filtering unchanged since we // don't have the wordpress methods in scope - $this->helper->method('apply_filters')->willReturnArgument(1); - $this->helper->method('sanitize_url')->willReturnArgument(0); - $this->helper->method('sanitize_text_field')->willReturnArgument(0); - $this->helper->method('esc_attr')->willReturnArgument(0); + WP_Mock::passthruFunction('sanitize_url'); + WP_Mock::passthruFunction('sanitize_text_field'); + WP_Mock::passthruFunction('esc_attr'); $this->duo_utils = $this->createMock(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class); - $this->duo_utils->wordpress_helper = $this->helper; } /** @@ -30,10 +29,10 @@ function setUp(): void */ public function testSettingsPageMultisite(): void { - $this->helper->method('is_multisite')->willReturn(true); - $this->helper->method('settings_fields')->willReturn(null); - $this->helper->method('do_settings_sections')->willReturn(null); - $this->helper->method('esc_attr_e')->willReturn(null); + WP_Mock::userFunction('is_multisite', ['return' => true]); + WP_Mock::userFunction('settings_fields', ['return' => null]); + WP_Mock::userFunction('do_settings_sections', ['return' => null]); + WP_Mock::userFunction('esc_attr_e', ['return' => null]); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $settings->duo_settings_page(); @@ -46,10 +45,10 @@ public function testSettingsPageMultisite(): void */ public function testSettingsPageSingleSite(): void { - $this->helper->method('is_multisite')->willReturn(false); - $this->helper->method('settings_fields')->willReturn(null); - $this->helper->method('do_settings_sections')->willReturn(null); - $this->helper->method('esc_attr_e')->willReturn(null); + WP_Mock::userFunction('is_multisite', ['return' => false]); + WP_Mock::userFunction('settings_fields', ['return' => null]); + WP_Mock::userFunction('do_settings_sections', ['return' => null]); + WP_Mock::userFunction('esc_attr_e', ['return' => null]); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $settings->duo_settings_page(); @@ -61,9 +60,9 @@ public function testSettingsPageSingleSite(): void */ public function testSettingsClientID(): void { - $this->helper->method('esc_attr')->willReturnArgument(0); + WP_Mock::passthruFunction('esc_attr'); + $duo_utils = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->helper)) ->onlyMethods(['duo_get_option']) ->getMock(); $duo_utils->method('duo_get_option')->willReturn("this-is-a-test-value"); @@ -78,7 +77,7 @@ public function testSettingsClientID(): void */ public function testDuoClientIDValidateInvalid(): void { - $this->helper->method('add_settings_error')->willReturn(null); + WP_Mock::userFunction('add_settings_error', ['return' => null]); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $result = $settings->duoup_client_id_validate("invalid id"); @@ -92,8 +91,8 @@ public function testDuoClientIDValidateInvalid(): void public function testDuoClientIDValidateInvalidNoClear(): void { $id = "this is an id"; - $this->helper->method('add_settings_error')->willReturn(null); - $this->helper->method('esc_attr')->willReturnArgument(0); + WP_Mock::userFunction('add_settings_error', ['return' => null]); + WP_Mock::passthruFunction('esc_attr'); $this->duo_utils->method('duo_get_option')->willReturn($id); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); @@ -107,7 +106,7 @@ public function testDuoClientIDValidateInvalidNoClear(): void */ public function testDuoClientIDValidateValid(): void { - $this->helper->method('add_settings_error')->willReturn(null); + WP_Mock::userFunction('add_settings_error', ['return' => null]); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $client_id = "DIXXXXXXXXXXXXXXXXXX"; @@ -121,9 +120,8 @@ public function testDuoClientIDValidateValid(): void */ public function testSettingsClientSecret(): void { - $this->helper->method('esc_attr')->willReturnArgument(0); + WP_Mock::passthruFunction('esc_attr'); $duo_utils = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->helper)) ->onlyMethods(['duo_get_option']) ->getMock(); $duo_utils->method('duo_get_option')->willReturn("this-is-a-fake-secret"); @@ -138,11 +136,10 @@ public function testSettingsClientSecret(): void */ public function testDuoClientSecretValidateInvalid(): void { - $this->helper->method('add_settings_error')->willReturn(null); + WP_Mock::userFunction('add_settings_error', ['return' => null]); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); - $this->helper->method('esc_attr')->willReturnArgument(0); + WP_Mock::passthruFunction('esc_attr'); $duo_utils = $this->createMock(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class); - $duo_utils->wordpress_helper = $this->helper; $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($duo_utils); $result = $settings->duoup_client_secret_validate("invalid secret"); @@ -168,11 +165,10 @@ public function testDuoClientSecretValidateDummyDoesntSave(): void */ public function testDuoClientSecretValidateValid(): void { - $this->helper->method('add_settings_error')->willReturn(null); - $this->helper->method('esc_attr')->willReturnArgument(0); + WP_Mock::userFunction('add_settings_error', ['return' => null]); + WP_Mock::passthruFunction('esc_attr'); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $duo_utils = $this->createMock(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class); - $duo_utils->wordpress_helper = $this->helper; $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($duo_utils); $client_secret = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; @@ -186,6 +182,7 @@ public function testDuoClientSecretValidateValid(): void */ public function testDuoClientSecretValidateInvalidNoClear(): void { + WP_Mock::userFunction('add_settings_error', ['return' => null]); $original_secret = "current secret that is 40 character long"; $this->duo_utils->method('duo_get_option')->willReturn($original_secret); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); @@ -215,11 +212,7 @@ public function testDuoHostInvalid(): void { $original_host = 'api-duo1.duo.test'; $this->duo_utils->method('duo_get_option')->willReturn($original_host); - $this->helper->method('add_settings_error')->willReturn(null); - - $this->helper->expects($this->once()) - ->method('add_settings_error') - ->with('duoup_api_host', '', 'Host is not valid'); + WP_Mock::userFunction('add_settings_error')->once()->with('duoup_api_host', '', 'Host is not valid')->andReturn(null); // All duo API hostnames start with 'api-' $invalid_host = 'api.duo.test'; @@ -238,11 +231,7 @@ public function testDuoHostDoubleApiPrefix(): void { $original_host = 'api-duo1.duo.test'; $this->duo_utils->method('duo_get_option')->willReturn($original_host); - $this->helper->method('add_settings_error')->willReturn(null); - - $this->helper->expects($this->once()) - ->method('add_settings_error') - ->with('duoup_api_host', '', 'Host is not valid'); + WP_Mock::userFunction('add_settings_error')->once()->with('duoup_api_host', '', 'Host is not valid')->andReturn(null); $invalid_host = 'api-api-duo1.duo.test'; $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); @@ -257,9 +246,8 @@ public function testDuoHostDoubleApiPrefix(): void */ public function testSettingsHostOutput(): void { - $this->helper->method('esc_attr')->willReturnArgument(0); + WP_Mock::passthruFunction('esc_attr'); $duo_utils = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->helper)) ->onlyMethods(['duo_get_option']) ->getMock(); $duo_utils->method('duo_get_option')->willReturn("this-is-a-test-host"); @@ -276,9 +264,8 @@ public function testSettingsHostOutput(): void */ public function testSettingsFailmode(): void { - $this->helper->method('esc_attr')->willReturnArgument(0); + WP_Mock::passthruFunction('esc_attr'); $duo_utils = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->helper)) ->onlyMethods(['duo_get_option']) ->getMock(); $duo_utils->method('duo_get_option')->willReturn("closed"); @@ -302,13 +289,12 @@ public function testSettingsRoles(): void ->addMethods(['get_names']) ->getMock(); $roles->method('get_names')->willReturn($duoup_roles); - $this->helper->method('before_last_bar')->willReturnArgument(0); + WP_Mock::passthruFunction('before_last_bar'); $duo_utils = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->helper)) ->onlyMethods(['duo_get_option', 'duo_get_roles']) ->getMock(); - $duo_utils->method('duo_get_option')->willReturn(["uses_2fa"]); + $duo_utils->method('duo_get_option')->willReturn(["uses_2fa" => true]); $duo_utils->method('duo_get_roles')->willReturn($roles); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($duo_utils); @@ -335,16 +321,12 @@ public function testSettingsRoles(): void */ public function testDuoSettingsXMLRPC(): void { - $helper = $this->getMockBuilder(stdClass::class) - ->addMethods(['esc_attr']) - ->getMock(); $duo_utils = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($helper)) ->onlyMethods(['duo_get_option']) ->getMock(); $duo_utils->method('duo_get_option')->willReturn('off'); - $helper->method('esc_attr')->willReturnArgument(0); + WP_Mock::passthruFunction('esc_attr'); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($duo_utils); $result = $settings->duo_settings_xmlrpc(); @@ -365,11 +347,7 @@ public function testDuoFailmodeInvalid(): void { $original_failmode = 'closed'; $this->duo_utils->method('duo_get_option')->willReturn($original_failmode); - $this->helper->method('add_settings_error')->willReturn(null); - - $this->helper->expects($this->once()) - ->method('add_settings_error') - ->with('duoup_failmode', '', 'Failmode value is not valid'); + WP_Mock::userFunction('add_settings_error')->once()->with('duoup_failmode', '', 'Failmode value is not valid')->andReturn(null); // All duo API hostnames start with 'api-' $invalid_failmode = 'foobar'; @@ -395,7 +373,7 @@ public function testDuoRolesValidateGood(): void ->getMock(); $roles->method('get_names')->willReturn($duoup_roles); - $this->helper->method('before_last_bar')->willReturnArgument(0); + WP_Mock::passthruFunction('before_last_bar'); $this->duo_utils->method('duo_get_roles')->willReturn($roles); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); @@ -414,7 +392,6 @@ public function testDuoRolesValidateGood(): void */ public function testDuoRolesValidateEmpty(): void { - $this->duo_utils->wordpress_helper = null; $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $this->assertEmpty($settings->duoup_roles_validate(1)); @@ -436,12 +413,8 @@ public function testDuoRolesValidateBadOptionsAreRemoved(): void ->addMethods(['get_names']) ->getMock(); $roles->method('get_names')->willReturn($duoup_roles); - $helper = $this->getMockBuilder(stdClass::class) - ->addMethods(['before_last_bar']) - ->getMock(); - $helper->method('before_last_bar')->willReturnArgument(0); + WP_Mock::passthruFunction('before_last_bar'); $duo_utils = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($helper)) ->onlyMethods(['duo_get_roles']) ->getMock(); @@ -462,12 +435,12 @@ public function testDuoRolesValidateBadOptionsAreRemoved(): void */ public function testDuoAddPageSingleSite(): void { - $this->helper->method('is_multisite')->willReturn(false); - $this->helper->method('add_options_page'); - $this->helper->expects($this->once())->method('add_options_page'); + WP_Mock::userFunction('is_multisite', ['return' => false]); + WP_Mock::userFunction('add_options_page')->once(); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $settings->duo_add_page(); + $this->assertConditionsMet(); } /** @@ -475,11 +448,12 @@ public function testDuoAddPageSingleSite(): void */ public function testDuoAddPageMultisite(): void { - $this->helper->method('is_multisite')->willReturn(true); - $this->helper->expects($this->never())->method('add_options_page'); + WP_Mock::userFunction('is_multisite', ['return' => true]); + WP_Mock::userFunction('add_options_page')->never(); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $settings->duo_add_page(); + $this->assertConditionsMet(); } /** @@ -488,10 +462,11 @@ public function testDuoAddPageMultisite(): void public function testDuoAddDuplicateSiteOption(): void { $this->duo_utils->method('duo_get_option')->willReturn(true); - $this->helper->expects($this->never())->method('add_site_option'); + WP_Mock::userFunction('add_site_option')->never(); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $settings->duo_add_site_option("FakeOption"); + $this->assertConditionsMet(); } /** @@ -500,13 +475,11 @@ public function testDuoAddDuplicateSiteOption(): void public function testDuoAddNewSiteOption(): void { $this->duo_utils->method('duo_get_option')->willReturn(false); - $this->helper - ->expects($this->once()) - ->method('add_site_option') - ->with("FakeOption"); + WP_Mock::userFunction('add_site_option')->once()->with("FakeOption", ""); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $settings->duo_add_site_option("FakeOption"); + $this->assertConditionsMet(); } /** @@ -524,8 +497,8 @@ public function testDuoAdminInitForMultisite(): void $roles->method('get_names')->willReturn($duoup_roles); $this->duo_utils->method('duo_get_roles')->willReturn($roles); - $this->helper->method('is_multisite')->willReturn(true); - $this->helper->method('before_last_bar')->will($this->returnArgument(0)); + WP_Mock::userFunction('is_multisite', ['return' => true]); + WP_Mock::passthruFunction('before_last_bar'); $settings = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Settings::class) ->setConstructorArgs(array($this->duo_utils)) @@ -552,37 +525,26 @@ public function testDuoAdminInitForMultisite(): void public function testDuoAdminInitForSingleSite(): void { - $this->helper->method('is_multisite')->willReturn(false); + WP_Mock::userFunction('is_multisite', ['return' => false]); $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); - $this->helper - ->expects($this->once()) - ->method('add_settings_section') - ->withConsecutive( - ['duo_universal_settings', 'Main Settings', array($settings, 'duo_settings_text'), 'duo_universal_settings'] - ); - $this->helper - ->expects($this->exactly(6)) - ->method('add_settings_field') - ->withConsecutive( - ['duoup_client_id', 'Client ID', array($settings, 'duo_settings_client_id'), 'duo_universal_settings', 'duo_universal_settings'], - ['duoup_client_secret', 'Client Secret', array($settings, 'duo_settings_client_secret'), 'duo_universal_settings', 'duo_universal_settings'], - ['duoup_api_host', 'API hostname', array($settings, 'duo_settings_host'), 'duo_universal_settings', 'duo_universal_settings'], - ['duoup_failmode', 'Failmode', array($settings, 'duo_settings_failmode'), 'duo_universal_settings', 'duo_universal_settings'], - ['duoup_roles', 'Enable for roles:', array($settings, 'duo_settings_roles'), 'duo_universal_settings', 'duo_universal_settings'], - ['duoup_xmlrpc', 'Disable XML-RPC (recommended)', array($settings, 'duo_settings_xmlrpc'), 'duo_universal_settings', 'duo_universal_settings'] - ); - $this->helper - ->expects($this->exactly(6)) - ->method('register_setting') - ->withConsecutive( - ['duo_universal_settings', 'duoup_client_id', array($settings, 'duoup_client_id_validate')], - ['duo_universal_settings', 'duoup_client_secret', array($settings, 'duoup_client_secret_validate')], - ['duo_universal_settings', 'duoup_api_host'], - ['duo_universal_settings', 'duoup_failmode'], - ['duo_universal_settings', 'duoup_roles', array($settings, 'duoup_roles_validate')], - ['duo_universal_settings', 'duoup_xmlrpc', array($settings, 'duoup_xmlrpc_validate')] - ); + WP_Mock::userFunction('add_settings_section')->once()->with('duo_universal_settings', 'Main Settings', array($settings, 'duo_settings_text'), 'duo_universal_settings'); + + WP_Mock::userFunction('add_settings_field')->once()->with('duoup_client_id', 'Client ID', array($settings, 'duo_settings_client_id'), 'duo_universal_settings', 'duo_universal_settings'); + WP_Mock::userFunction('add_settings_field')->once()->with('duoup_client_secret', 'Client Secret', array($settings, 'duo_settings_client_secret'), 'duo_universal_settings', 'duo_universal_settings'); + WP_Mock::userFunction('add_settings_field')->once()->with('duoup_api_host', 'API hostname', array($settings, 'duo_settings_host'), 'duo_universal_settings', 'duo_universal_settings'); + WP_Mock::userFunction('add_settings_field')->once()->with('duoup_failmode', 'Failmode', array($settings, 'duo_settings_failmode'), 'duo_universal_settings', 'duo_universal_settings'); + WP_Mock::userFunction('add_settings_field')->once()->with('duoup_roles', 'Enable for roles:', array($settings, 'duo_settings_roles'), 'duo_universal_settings', 'duo_universal_settings'); + WP_Mock::userFunction('add_settings_field')->once()->with('duoup_xmlrpc', 'Disable XML-RPC (recommended)', array($settings, 'duo_settings_xmlrpc'), 'duo_universal_settings', 'duo_universal_settings'); + + WP_Mock::userFunction('register_setting')->once()->with('duo_universal_settings', 'duoup_client_id', array($settings, 'duoup_client_id_validate')); + WP_Mock::userFunction('register_setting')->once()->with('duo_universal_settings', 'duoup_client_secret', array($settings, 'duoup_client_secret_validate')); + WP_Mock::userFunction('register_setting')->once()->with('duo_universal_settings', 'duoup_api_host', array($settings, 'duoup_api_host_validate')); + WP_Mock::userFunction('register_setting')->once()->with('duo_universal_settings', 'duoup_failmode', array($settings, 'duoup_failmode_validate')); + WP_Mock::userFunction('register_setting')->once()->with('duo_universal_settings', 'duoup_roles', array($settings, 'duoup_roles_validate')); + WP_Mock::userFunction('register_setting')->once()->with('duo_universal_settings', 'duoup_xmlrpc', array($settings, 'duoup_xmlrpc_validate')); + $settings->duo_admin_init(); + $this->assertConditionsMet(); } /** @@ -610,20 +572,18 @@ public function testDuoMultisiteUpdateWithPostValues(): void 'duoup_xmlrpc' => 'off' ); - $this->helper - ->expects($this->exactly(6)) - ->method('update_site_option') - ->withConsecutive( - ['duoup_client_id', 'DIAAAAAAAAAAAAAAAAAA'], - ['duoup_client_secret', str_repeat('aBc123As3cr3t4uandme', 2)], - ['duoup_api_host', 'api-duo1.duo.test'], - ['duoup_failmode', 'closed'], - ['duoup_roles', $duoup_roles], - ['duoup_xmlrpc', 'off'], - ); + WP_Mock::userFunction('update_site_option')->once()->with('duoup_client_id', 'DIAAAAAAAAAAAAAAAAAA'); + WP_Mock::userFunction('update_site_option')->once()->with('duoup_client_secret', str_repeat('aBc123As3cr3t4uandme', 2)); + WP_Mock::userFunction('update_site_option')->once()->with('duoup_api_host', 'api-duo1.duo.test'); + WP_Mock::userFunction('update_site_option')->once()->with('duoup_failmode', 'closed'); + WP_Mock::userFunction('update_site_option')->once()->with('duoup_roles', $duoup_roles); + WP_Mock::userFunction('update_site_option')->once()->with('duoup_xmlrpc', 'off'); + WP_Mock::passthruFunction('wp_unslash'); + $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $settings->duo_update_mu_options(); $_POST = $this->old_POST; + $this->assertConditionsMet(); } /** @@ -631,16 +591,13 @@ public function testDuoMultisiteUpdateWithPostValues(): void */ public function testDuoMultisiteUpdateWithEmptyPostValue(): void { - $this->helper - ->expects($this->exactly(3)) - ->method('update_site_option') - ->withConsecutive( - ['duoup_failmode', 'open'], - ['duoup_roles', []], - ['duoup_xmlrpc', 'on'], - ); + WP_Mock::userFunction('update_site_option')->once()->with('duoup_failmode', 'open'); + WP_Mock::userFunction('update_site_option')->once()->with('duoup_roles', []); + WP_Mock::userFunction('update_site_option')->once()->with('duoup_xmlrpc', 'on'); + $settings = new Duo\DuoUniversalWordpress\DuoUniversal_Settings($this->duo_utils); $settings->duo_update_mu_options(); + $this->assertConditionsMet(); } } diff --git a/tests/duoUniversalUtilitiesTest.php b/tests/duoUniversalUtilitiesTest.php index 906961e..3fcf24a 100644 --- a/tests/duoUniversalUtilitiesTest.php +++ b/tests/duoUniversalUtilitiesTest.php @@ -4,16 +4,9 @@ use Duo\DuoUniversalWordpress; use PHPUnit\Framework\TestCase; require_once 'class-duouniversal-utilities.php'; -require_once 'class-duouniversal-wordpresshelper.php'; final class UtilitiesTest extends TestCase { - protected function setUp(): void - { - $helper = new Duo\DuoUniversalWordpress\DuoUniversal_WordpressHelper(); - $this->wordpress_helper = $helper; - } - /** * Test that test_duo_auth_enabled returns false * when XMLRPC_REQUEST is defined @@ -21,7 +14,6 @@ protected function setUp(): void public function testDuoAuthXMLRPCEnabled(): void { $command = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->wordpress_helper)) ->onlyMethods(['xmlrpc_enabled']) ->getMock(); $command->method('xmlrpc_enabled')->willReturn(true); @@ -37,7 +29,6 @@ public function testDuoAuthXMLRPCEnabled(): void public function testDuoAuthMissingOptions(): void { $command = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->wordpress_helper)) ->onlyMethods(['duo_debug_log', 'duo_get_option']) ->getMock(); $command->method('duo_get_option')->willReturn(''); @@ -53,7 +44,6 @@ public function testDuoAuthMissingOptions(): void public function testDuoAuthHappyPath(): void { $command = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->wordpress_helper)) ->onlyMethods(['duo_debug_log', 'duo_get_option', 'xmlrpc_enabled']) ->getMock(); $command->method('duo_get_option')->willReturn(true); @@ -79,7 +69,6 @@ public function testDuoRoleRequireMFAEmpty(): void ); $command = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->wordpress_helper)) ->onlyMethods(['duo_get_option', 'duo_get_roles']) ->getMock(); $command->method('duo_get_option')->willReturn([]); @@ -106,7 +95,6 @@ public function testDuoRoleRequireRoleDisabled(): void $roles->method('get_names')->willReturn($duoup_roles); $command = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->wordpress_helper)) ->onlyMethods(['duo_get_roles', 'duo_get_option']) ->getMock(); $command->method('duo_get_option')->willReturn($duoup_roles); @@ -133,7 +121,6 @@ public function testDuoRoleRequireRoleEnabled(): void $roles->method('get_names')->willReturn($duoup_roles); $command = $this->getMockBuilder(Duo\DuoUniversalWordpress\DuoUniversal_Utilities::class) - ->setConstructorArgs(array($this->wordpress_helper)) ->onlyMethods(['duo_get_roles', 'duo_get_option']) ->getMock(); $command->method('duo_get_option')->willReturn($duoup_roles); @@ -151,13 +138,10 @@ public function testDuoRoleRequireRoleEnabled(): void */ public function testDuoGetOptionSingleSite(): void { - $helper = $this->getMockBuilder(stdClass::class) - ->addMethods(['is_multisite', 'get_option']) - ->getMock(); - $helper->method('is_multisite')->willReturn(false); - $helper->expects($this->once())->method('get_option')->willReturn("value"); + WP_Mock::userFunction('is_multisite', [ 'return' => false ]); + WP_Mock::userFunction('get_option', [ 'return' => "value" ])->once(); - $duo_utils = new Duo\DuoUniversalWordpress\DuoUniversal_Utilities($helper); + $duo_utils = new Duo\DuoUniversalWordpress\DuoUniversal_Utilities(); $this->assertEquals($duo_utils->duo_get_option("test"), 'value'); } @@ -167,13 +151,10 @@ public function testDuoGetOptionSingleSite(): void */ public function testDuoGetOptionMultiSite(): void { - $helper = $this->getMockBuilder(stdClass::class) - ->addMethods(['is_multisite', 'get_site_option']) - ->getMock(); - $helper->method('is_multisite')->willReturn(true); - $helper->expects($this->once())->method('get_site_option')->willReturn("value"); + WP_Mock::userFunction('is_multisite', [ 'return' => true ]); + WP_Mock::userFunction('get_site_option', [ 'return' => "value" ])->once(); - $duo_utils = new Duo\DuoUniversalWordpress\DuoUniversal_Utilities($helper); + $duo_utils = new Duo\DuoUniversalWordpress\DuoUniversal_Utilities(); $this->assertEquals($duo_utils->duo_get_option("test"), 'value'); } } diff --git a/uninstall.php b/uninstall.php index 1ce354a..9385f78 100644 --- a/uninstall.php +++ b/uninstall.php @@ -1,4 +1,16 @@