WordPress.org

Make WordPress Core

Ticket #31616: 31616.2.diff

File 31616.2.diff, 20.7 KB (added by jipmoors, 7 years ago)

Added unittests for usable_filesystem_credentials and get_filesystem_credentials. Fixed array index checks because of filtered credential overrides.

  • src/wp-admin/includes/file.php

     
    910910
    911911        $context = trailingslashit( $context );
    912912
    913         if ( ! $method ) {
     913        static $results = array();
    914914
    915                 $temp_file_name = $context . 'temp-write-test-' . time();
    916                 $temp_handle = @fopen($temp_file_name, 'w');
    917                 if ( $temp_handle ) {
     915        // saving hash for static cache; file reads/writes are to be avoided when possible
     916        $hash = md5(serialize($args) . $context . $allow_relaxed_file_ownership);
     917        if ( ! isset($results[$hash])) {
    918918
    919                         // Attempt to determine the file owner of the WordPress files, and that of newly created files
    920                         $wp_file_owner = $temp_file_owner = false;
    921                         if ( function_exists('fileowner') ) {
    922                                 $wp_file_owner = @fileowner( __FILE__ );
    923                                 $temp_file_owner = @fileowner( $temp_file_name );
    924                         }
     919                if ( ! $method ) {
    925920
    926                         if ( $wp_file_owner !== false && $wp_file_owner === $temp_file_owner ) {
    927                                 // WordPress is creating files as the same owner as the WordPress files,
    928                                 // this means it's safe to modify & create new files via PHP.
    929                                 $method = 'direct';
    930                                 $GLOBALS['_wp_filesystem_direct_method'] = 'file_owner';
    931                         } elseif ( $allow_relaxed_file_ownership ) {
    932                                 // The $context directory is writable, and $allow_relaxed_file_ownership is set, this means we can modify files
    933                                 // safely in this directory. This mode doesn't create new files, only alter existing ones.
    934                                 $method = 'direct';
    935                                 $GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership';
     921                        $temp_file_name = $context . 'temp-write-test-' . time();
     922                        $temp_handle    = @fopen( $temp_file_name, 'w' );
     923                        if ( $temp_handle ) {
     924
     925                                // Attempt to determine the file owner of the WordPress files, and that of newly created files
     926                                $wp_file_owner = $temp_file_owner = false;
     927                                if ( function_exists( 'fileowner' ) ) {
     928                                        $wp_file_owner   = @fileowner( __FILE__ );
     929                                        $temp_file_owner = @fileowner( $temp_file_name );
     930                                }
     931
     932                                if ( $wp_file_owner !== false && $wp_file_owner === $temp_file_owner ) {
     933                                        // WordPress is creating files as the same owner as the WordPress files,
     934                                        // this means it's safe to modify & create new files via PHP.
     935                                        $method                                  = 'direct';
     936                                        $GLOBALS['_wp_filesystem_direct_method'] = 'file_owner';
     937                                } elseif ( $allow_relaxed_file_ownership ) {
     938                                        // The $context directory is writable, and $allow_relaxed_file_ownership is set, this means we can modify files
     939                                        // safely in this directory. This mode doesn't create new files, only alter existing ones.
     940                                        $method                                  = 'direct';
     941                                        $GLOBALS['_wp_filesystem_direct_method'] = 'relaxed_ownership';
     942                                }
     943
     944                                @fclose( $temp_handle );
     945                                @unlink( $temp_file_name );
    936946                        }
     947                }
    937948
    938                         @fclose($temp_handle);
    939                         @unlink($temp_file_name);
     949                if ( ! $method && isset($args['connection_type']) && 'ssh' == $args['connection_type'] && extension_loaded('ssh2') && function_exists('stream_get_contents') ) $method = 'ssh2';
     950                if ( ! $method && extension_loaded('ftp') ) $method = 'ftpext';
     951                if ( ! $method && ( extension_loaded('sockets') || function_exists('fsockopen') ) ) $method = 'ftpsockets'; //Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread
     952
     953                /**
     954                 * Filter the filesystem method to use.
     955                 *
     956                 * @since 2.6.0
     957                 *
     958                 * @param string $method  Filesystem method to return.
     959                 * @param array  $args    An array of connection details for the method.
     960                 * @param string $context Full path to the directory that is tested for being writable.
     961                 * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
     962                 */
     963                $results[$hash] = apply_filters( 'filesystem_method', $method, $args, $context, $allow_relaxed_file_ownership );
     964        }
     965
     966        return $results[$hash];
     967}
     968
     969/**
     970 * Get the filesystem credentials needed to connect to the site
     971 *
     972 * @param string  $form_post    the URL to post the form to
     973 * @param string  $type         the chosen Filesystem method in use
     974 * @param boolean $error        if the current request has failed to connect
     975 * @param string  $context      The directory which is needed access to,
     976 *                              The write-test will be performed on this
     977 *                              directory by get_filesystem_method()
     978 * @param array   $extra_fields Extra POST fields which should be checked for to be included in the post.
     979 * @param bool    $allow_relaxed_file_ownership Whether to allow Group/World writable.
     980 * @return mixed Credentials that are found, not guaranteed to contain all information
     981 */
     982function get_filesystem_credentials($form_post, $type = '', $error = false, $context = false, $extra_fields = null, $allow_relaxed_file_ownership = false) {
     983
     984        /** This filter is documented in wp-includes/file.php */
     985        $credentials = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
     986        if ( '' !== $credentials ) {
     987                return $credentials;
     988        }
     989
     990        if ( empty($type) ) {
     991                $type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
     992        }
     993
     994        if ( 'direct' == $type ) {
     995                return true;
     996        }
     997
     998        $credentials = get_option('ftp_credentials', array( 'hostname' => '', 'username' => ''));
     999
     1000        // If defined, set it to that, Else, If POST'd, set it to that, If not, Set it to whatever it previously was(saved details in option)
     1001        $credentials['hostname'] = defined('FTP_HOST') ? FTP_HOST : (!empty($_POST['hostname']) ? wp_unslash( $_POST['hostname'] ) : $credentials['hostname']);
     1002        $credentials['username'] = defined('FTP_USER') ? FTP_USER : (!empty($_POST['username']) ? wp_unslash( $_POST['username'] ) : $credentials['username']);
     1003        $credentials['password'] = defined('FTP_PASS') ? FTP_PASS : (!empty($_POST['password']) ? wp_unslash( $_POST['password'] ) : '');
     1004
     1005        // Check to see if we are setting the public/private keys for ssh
     1006        $credentials['public_key'] = defined('FTP_PUBKEY') ? FTP_PUBKEY : (!empty($_POST['public_key']) ? wp_unslash( $_POST['public_key'] ) : '');
     1007        $credentials['private_key'] = defined('FTP_PRIKEY') ? FTP_PRIKEY : (!empty($_POST['private_key']) ? wp_unslash( $_POST['private_key'] ) : '');
     1008
     1009        // Sanitize the hostname, Some people might pass in odd-data:
     1010        $credentials['hostname'] = preg_replace('|\w+://|', '', $credentials['hostname']); //Strip any schemes off
     1011
     1012        if ( strpos($credentials['hostname'], ':') ) {
     1013                list( $credentials['hostname'], $credentials['port'] ) = explode(':', $credentials['hostname'], 2);
     1014                if ( ! is_numeric($credentials['port']) )
     1015                        unset($credentials['port']);
     1016        } else {
     1017                unset($credentials['port']);
     1018        }
     1019
     1020        if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' == FS_METHOD ) ) {
     1021                $credentials['connection_type'] = 'ssh';
     1022        } elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' == $type ) { //Only the FTP Extension understands SSL
     1023                $credentials['connection_type'] = 'ftps';
     1024        } elseif ( ! empty( $_POST['connection_type'] ) ) {
     1025                $credentials['connection_type'] = wp_unslash( $_POST['connection_type'] );
     1026        } elseif ( ! isset( $credentials['connection_type'] ) ) { //All else fails (And it's not defaulted to something else saved), Default to FTP
     1027                $credentials['connection_type'] = 'ftp';
     1028        }
     1029
     1030        return $credentials;
     1031}
     1032
     1033/**
     1034 * @param array  $credentials   Credentials to check required parameters
     1035 * @param string $type          Type of connection to check credentials
     1036 *                              against (direct, ftps, ftp, ssh, etc.)
     1037 * @param string $context       The directory which is needed access to,
     1038 *                              the write-test will be performed on this
     1039 *                              directory by get_filesystem_method()
     1040 * @param bool   $allow_relaxed_file_ownership  Whether to allow Group/World writable.
     1041 *
     1042 * @return bool  True when all required parameters have been set for supplied type, False when anything is missing
     1043 */
     1044function usable_filesystem_credentials($credentials, $type = '', $context = null, $allow_relaxed_file_ownership = false) {
     1045        if ( empty($type) ) {
     1046                $type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
     1047        }
     1048
     1049        // Direct connection does not need any credentials
     1050        if ( 'direct' == $type ) {
     1051                return true;
     1052        }
     1053
     1054        // We need something to check against
     1055        if ( empty( $credentials ) ) {
     1056                return false;
     1057        }
     1058
     1059        // All the checks required an array for parameters; exit if we got something else
     1060        if ( ! is_array( $credentials ) ) {
     1061                return false;
     1062        }
     1063
     1064        // SSH needs public and private key
     1065        if ( isset($credentials['connection_type']) && 'ssh' == $credentials['connection_type'] ) {
     1066                if ( ! isset( $credentials['public_key'], $credentials['private_key'] ) ) {
     1067                        return false;
    9401068                }
    941         }
    9421069
    943         if ( ! $method && isset($args['connection_type']) && 'ssh' == $args['connection_type'] && extension_loaded('ssh2') && function_exists('stream_get_contents') ) $method = 'ssh2';
    944         if ( ! $method && extension_loaded('ftp') ) $method = 'ftpext';
    945         if ( ! $method && ( extension_loaded('sockets') || function_exists('fsockopen') ) ) $method = 'ftpsockets'; //Sockets: Socket extension; PHP Mode: FSockopen / fwrite / fread
     1070                return ( ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] ) );
     1071        }
    9461072
    947         /**
    948          * Filter the filesystem method to use.
    949          *
    950          * @since 2.6.0
    951          *
    952          * @param string $method  Filesystem method to return.
    953          * @param array  $args    An array of connection details for the method.
    954          * @param string $context Full path to the directory that is tested for being writable.
    955          * @param bool   $allow_relaxed_file_ownership Whether to allow Group/World writable.
    956          */
    957         return apply_filters( 'filesystem_method', $method, $args, $context, $allow_relaxed_file_ownership );
     1073        // Other connection methods need hostname, password and username
     1074        if ( ! isset( $credentials['password'], $credentials['username'], $credentials['hostname'] ) ) {
     1075                return false;
     1076        }
     1077
     1078        return ( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) );
    9581079}
    9591080
    9601081/**
     
    9821103 * @return boolean False on failure. True on success.
    9831104 */
    9841105function request_filesystem_credentials($form_post, $type = '', $error = false, $context = false, $extra_fields = null, $allow_relaxed_file_ownership = false ) {
    985 
    9861106        /**
    9871107         * Filter the filesystem credentials form output.
    9881108         *
     
    10011121         * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable.
    10021122         * @param array  $extra_fields Extra POST fields.
    10031123         */
    1004         $req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
    1005         if ( '' !== $req_cred )
    1006                 return $req_cred;
     1124        $credentials = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
     1125        if ( '' !== $credentials ) {
     1126                return $credentials;
     1127        }
    10071128
    1008         if ( empty($type) ) {
     1129        if ( empty( $type ) ) {
    10091130                $type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
    10101131        }
    10111132
    1012         if ( 'direct' == $type )
     1133        if ( 'direct' == $type ) {
    10131134                return true;
     1135        }
    10141136
    1015         if ( is_null( $extra_fields ) )
    1016                 $extra_fields = array( 'version', 'locale' );
     1137        $credentials = get_filesystem_credentials( $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
    10171138
    1018         $credentials = get_option('ftp_credentials', array( 'hostname' => '', 'username' => ''));
     1139        if ( ! $error && usable_filesystem_credentials( $credentials, $type ) ) {
    10191140
    1020         // If defined, set it to that, Else, If POST'd, set it to that, If not, Set it to whatever it previously was(saved details in option)
    1021         $credentials['hostname'] = defined('FTP_HOST') ? FTP_HOST : (!empty($_POST['hostname']) ? wp_unslash( $_POST['hostname'] ) : $credentials['hostname']);
    1022         $credentials['username'] = defined('FTP_USER') ? FTP_USER : (!empty($_POST['username']) ? wp_unslash( $_POST['username'] ) : $credentials['username']);
    1023         $credentials['password'] = defined('FTP_PASS') ? FTP_PASS : (!empty($_POST['password']) ? wp_unslash( $_POST['password'] ) : '');
    1024 
    1025         // Check to see if we are setting the public/private keys for ssh
    1026         $credentials['public_key'] = defined('FTP_PUBKEY') ? FTP_PUBKEY : (!empty($_POST['public_key']) ? wp_unslash( $_POST['public_key'] ) : '');
    1027         $credentials['private_key'] = defined('FTP_PRIKEY') ? FTP_PRIKEY : (!empty($_POST['private_key']) ? wp_unslash( $_POST['private_key'] ) : '');
    1028 
    1029         // Sanitize the hostname, Some people might pass in odd-data:
    1030         $credentials['hostname'] = preg_replace('|\w+://|', '', $credentials['hostname']); //Strip any schemes off
    1031 
    1032         if ( strpos($credentials['hostname'], ':') ) {
    1033                 list( $credentials['hostname'], $credentials['port'] ) = explode(':', $credentials['hostname'], 2);
    1034                 if ( ! is_numeric($credentials['port']) )
    1035                         unset($credentials['port']);
    1036         } else {
    1037                 unset($credentials['port']);
    1038         }
    1039 
    1040         if ( ( defined( 'FTP_SSH' ) && FTP_SSH ) || ( defined( 'FS_METHOD' ) && 'ssh2' == FS_METHOD ) ) {
    1041                 $credentials['connection_type'] = 'ssh';
    1042         } elseif ( ( defined( 'FTP_SSL' ) && FTP_SSL ) && 'ftpext' == $type ) { //Only the FTP Extension understands SSL
    1043                 $credentials['connection_type'] = 'ftps';
    1044         } elseif ( ! empty( $_POST['connection_type'] ) ) {
    1045                 $credentials['connection_type'] = wp_unslash( $_POST['connection_type'] );
    1046         } elseif ( ! isset( $credentials['connection_type'] ) ) { //All else fails (And it's not defaulted to something else saved), Default to FTP
    1047                 $credentials['connection_type'] = 'ftp';
    1048         }
    1049         if ( ! $error &&
    1050                         (
    1051                                 ( !empty($credentials['password']) && !empty($credentials['username']) && !empty($credentials['hostname']) ) ||
    1052                                 ( 'ssh' == $credentials['connection_type'] && !empty($credentials['public_key']) && !empty($credentials['private_key']) )
    1053                         ) ) {
    10541141                $stored_credentials = $credentials;
    1055                 if ( !empty($stored_credentials['port']) ) //save port as part of hostname to simplify above code.
     1142                if ( ! empty( $stored_credentials['port'] ) ) //save port as part of hostname to simplify retrievement code
     1143                {
    10561144                        $stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
     1145                }
    10571146
    1058                 unset($stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key']);
     1147                unset( $stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key'] );
     1148
    10591149                if ( ! defined( 'WP_INSTALLING' ) ) {
    10601150                        update_option( 'ftp_credentials', $stored_credentials );
    10611151                }
     1152
    10621153                return $credentials;
    10631154        }
     1155
     1156        // Display the credentials form to the user
     1157        request_filesystem_credentials_form( $form_post, $credentials, $type, $error, $context, $extra_fields );
     1158
     1159        // Nothing usable has been found yet.
     1160        return false;
     1161}
     1162
     1163/**
     1164 * Write the form needed to input credentials needed to connect for the specified type
     1165 *
     1166 * @param string $form_post the URL to post the form to
     1167 * @param $credentials
     1168 * @param string $type the chosen Filesystem method in use
     1169 * @param boolean $error if the current request has failed to connect
     1170 * @param string $context The directory which is needed access to, The write-test will be performed on this directory by get_filesystem_method()
     1171 * @param array $extra_fields Extra POST fields which should be checked for to be included in the post.
     1172 * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable.
     1173 */
     1174function request_filesystem_credentials_form($form_post, $credentials, $type = '', $error = false, $context = null, $extra_fields = null, $allow_relaxed_file_ownership = false) {
     1175        if ( empty( $type ) ) {
     1176                $type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
     1177        }
     1178
     1179        if ( ! is_array($credentials)) {
     1180                $credentials = array();
     1181        }
     1182
    10641183        $hostname = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
    10651184        $username = isset( $credentials['username'] ) ? $credentials['username'] : '';
    10661185        $public_key = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
     
    10681187        $port = isset( $credentials['port'] ) ? $credentials['port'] : '';
    10691188        $connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
    10701189
     1190        if ( is_null( $extra_fields ) )
     1191                $extra_fields = array( 'version', 'locale' );
     1192
    10711193        if ( $error ) {
    10721194                $error_string = __('<strong>ERROR:</strong> There was an error connecting to the server, Please verify the settings are correct.');
    10731195                if ( is_wp_error($error) )
     
    11901312</div>
    11911313</form>
    11921314<?php
    1193         return false;
     1315
    11941316}
  • tests/phpunit/tests/filesystem/credentials.php

     
     1<?php
     2
     3/**
     4 * @group filesystem
     5 * @group wp-filesystem
     6 */
     7
     8class Tests_Filesystem_Credentials extends WP_UnitTestCase {
     9        /**
     10         *
     11         */
     12        function test_get_filesystem_credentials() {
     13                // test direct type
     14                $credentials = get_filesystem_credentials('', 'direct');
     15                $this->assertTrue( $credentials );
     16
     17                // test SSH
     18                $credentials = get_filesystem_credentials('', 'ssh');
     19                $this->assertInternalType( 'array', $credentials );
     20
     21                $this->assertArrayHasKey( 'connection_type', $credentials );
     22                $this->assertArrayHasKey( 'private_key', $credentials );
     23                $this->assertArrayHasKey( 'public_key', $credentials );
     24                $this->assertArrayHasKey( 'hostname', $credentials );
     25                $this->assertArrayHasKey( 'username', $credentials );
     26                $this->assertArrayHasKey( 'password', $credentials );
     27
     28                $this->assertEquals( '', $credentials['private_key'] );
     29                $this->assertEquals( '', $credentials['public_key'] );
     30                $this->assertEquals( '', $credentials['hostname'] );
     31                $this->assertEquals( '', $credentials['username'] );
     32                $this->assertEquals( '', $credentials['password'] );
     33
     34                $_POST['hostname'] = rand_str();
     35                $_POST['private_key'] = rand_str();
     36                $_POST['public_key'] = rand_str();
     37                $_POST['username'] = rand_str();
     38                $_POST['password'] = rand_str();
     39
     40                $credentials = get_filesystem_credentials('', 'ftp');
     41
     42                $this->assertEquals( $_POST['private_key'], $credentials['private_key'] );
     43                $this->assertEquals( $_POST['public_key'], $credentials['public_key'] );
     44                $this->assertEquals( $_POST['hostname'], $credentials['hostname'] );
     45                $this->assertEquals( $_POST['username'], $credentials['username'] );
     46                $this->assertEquals( $_POST['password'], $credentials['password'] );
     47
     48                unset($_POST['hostname'], $_POST['private_key'], $_POST['public_key'], $_POST['username'], $_POST['password']);
     49
     50
     51                // test filter override:
     52                add_filter( 'request_filesystem_credentials', array('Tests_Filesystem_Credentials', '__filesystem_credentials'), 10, 3);
     53
     54                $credentials = get_filesystem_credentials('', 'ssh');
     55                $_credentials = Tests_Filesystem_Credentials::__filesystem_credentials('', '', 'ssh');
     56
     57                $this->assertEquals( $_credentials, $credentials);
     58
     59                // clean up filter
     60                remove_filter( 'request_filesystem_credentials', array('Tests_Filesystem_Credentials', '__filesystem_credentials'), 10, 3);
     61        }
     62
     63        /**
     64         *
     65         */
     66        function test_usable_filesystem_credentials() {
     67                $type = 'direct';
     68                $credentials = true;
     69
     70                $this->assertTrue( usable_filesystem_credentials( $credentials, $type ) );
     71
     72                // ssh: only one that needs public/private keys
     73                $credentials = get_filesystem_credentials( '', 'ssh' );
     74                $credentials['connection_type'] = 'ssh';
     75
     76                $this->assertFalse( usable_filesystem_credentials( $credentials, 'ssh' ) );
     77
     78                $credentials['private_key'] = $credentials['public_key'] = 'a_value';
     79                $this->assertTrue( usable_filesystem_credentials( $credentials, 'ssh' ) );
     80
     81                // ssh2
     82                $credentials['connection_type'] = 'ssh2';
     83                $this->assertFalse( usable_filesystem_credentials( $credentials, 'ssh2' ) );
     84
     85                $credentials = array(
     86                        'hostname' => 'a',
     87                        'username' => 'b',
     88                        'password' => 'c'
     89                );
     90                $this->assertTrue( usable_filesystem_credentials( $credentials, 'ssh2' ) );
     91
     92
     93                // ftp
     94                $credentials = array(
     95                        'hostname' => '',
     96                        'username' => '',
     97                        'password' => ''
     98                );
     99                $this->assertFalse( usable_filesystem_credentials( $credentials, 'ftp' ) );
     100
     101                $credentials = array(
     102                        'hostname' => 'a',
     103                        'username' => 'b',
     104                        'password' => 'c'
     105                );
     106                $this->assertTrue( usable_filesystem_credentials( $credentials, 'ftp' ) );
     107        }
     108
     109        /** Helper functions */
     110        public static function __filesystem_credentials($empty, $form_url, $type) {
     111                switch ($type) {
     112                        case 'ssh':
     113                                $credentials = array(
     114                                        'connection_type' => 'ssh',
     115                                        'private_key' => 'b',
     116                                        'public_key' => 'c'
     117                                );
     118                                break;
     119                        default:
     120                                $credentials = array(
     121                                        'hostname' => 'a',
     122                                        'username' => 'b',
     123                                        'password' => 'c'
     124                                );
     125                                break;
     126                }
     127
     128                return $credentials;
     129        }
     130}
     131