WordPress.org

Make WordPress Core

Ticket #31616: 31616.3.diff

File 31616.3.diff, 20.5 KB (added by jipmoors, 7 years ago)

get_filesystem_credentials returns array for type 'direct' with proper connection_type

  • 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 array( 'connection_type' => 'direct' );
     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 * Check credentials for required parameters
     1035 *
     1036 * @param array  $credentials   Credentials to check required parameters
     1037 *
     1038 * @return bool  True when all required parameters have been set for supplied type, False when anything is missing
     1039 */
     1040function usable_filesystem_credentials($credentials) {
     1041        // We need something to check against
     1042        if ( empty( $credentials ) ) {
     1043                return false;
     1044        }
     1045
     1046        // All the checks required an array for parameters; exit if we got something else
     1047        if ( ! is_array( $credentials ) ) {
     1048                return false;
     1049        }
     1050
     1051        if ( 'direct' == $credentials['connection_type'] ) {
     1052                return true;
     1053        }
     1054
     1055        // SSH needs public and private key
     1056        if ( isset($credentials['connection_type']) && 'ssh' == $credentials['connection_type'] ) {
     1057                if ( ! isset( $credentials['public_key'], $credentials['private_key'] ) ) {
     1058                        return false;
    9401059                }
    941         }
    9421060
    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
     1061                return ( ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] ) );
     1062        }
    9461063
    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 );
     1064        // Other connection methods need hostname, password and username
     1065        if ( ! isset( $credentials['password'], $credentials['username'], $credentials['hostname'] ) ) {
     1066                return false;
     1067        }
     1068
     1069        return ( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) );
    9581070}
    9591071
    9601072/**
     
    9821094 * @return boolean False on failure. True on success.
    9831095 */
    9841096function request_filesystem_credentials($form_post, $type = '', $error = false, $context = false, $extra_fields = null, $allow_relaxed_file_ownership = false ) {
    985 
    9861097        /**
    9871098         * Filter the filesystem credentials form output.
    9881099         *
     
    10011112         * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable.
    10021113         * @param array  $extra_fields Extra POST fields.
    10031114         */
    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;
     1115        $credentials = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
     1116        if ( '' !== $credentials ) {
     1117                return $credentials;
     1118        }
    10071119
    1008         if ( empty($type) ) {
     1120        if ( empty( $type ) ) {
    10091121                $type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
    10101122        }
    10111123
    1012         if ( 'direct' == $type )
     1124        if ( 'direct' == $type ) {
    10131125                return true;
     1126        }
    10141127
    1015         if ( is_null( $extra_fields ) )
    1016                 $extra_fields = array( 'version', 'locale' );
     1128        $credentials = get_filesystem_credentials( $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership );
    10171129
    1018         $credentials = get_option('ftp_credentials', array( 'hostname' => '', 'username' => ''));
     1130        if ( 'direct' == $credentials['connection_type'] ) {
     1131                return true;
     1132        }
    10191133
    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'] ) : '');
     1134        if ( ! $error && usable_filesystem_credentials( $credentials ) ) {
    10241135
    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                         ) ) {
    10541136                $stored_credentials = $credentials;
    1055                 if ( !empty($stored_credentials['port']) ) //save port as part of hostname to simplify above code.
     1137                if ( ! empty( $stored_credentials['port'] ) ) //save port as part of hostname to simplify retrievement code
     1138                {
    10561139                        $stored_credentials['hostname'] .= ':' . $stored_credentials['port'];
     1140                }
    10571141
    1058                 unset($stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key']);
     1142                unset( $stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key'] );
     1143
    10591144                if ( ! defined( 'WP_INSTALLING' ) ) {
    10601145                        update_option( 'ftp_credentials', $stored_credentials );
    10611146                }
     1147
    10621148                return $credentials;
    10631149        }
     1150
     1151        // Display the credentials form to the user
     1152        request_filesystem_credentials_form( $form_post, $credentials, $type, $error, $context, $extra_fields );
     1153
     1154        // Nothing usable has been found yet.
     1155        return false;
     1156}
     1157
     1158/**
     1159 * Write the form needed to input credentials needed to connect for the specified type
     1160 *
     1161 * @param string $form_post the URL to post the form to
     1162 * @param $credentials
     1163 * @param string $type the chosen Filesystem method in use
     1164 * @param boolean $error if the current request has failed to connect
     1165 * @param string $context The directory which is needed access to, The write-test will be performed on this directory by get_filesystem_method()
     1166 * @param array $extra_fields Extra POST fields which should be checked for to be included in the post.
     1167 * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable.
     1168 */
     1169function request_filesystem_credentials_form($form_post, $credentials, $type = '', $error = false, $context = null, $extra_fields = null, $allow_relaxed_file_ownership = false) {
     1170        if ( empty( $type ) ) {
     1171                $type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership );
     1172        }
     1173
     1174        if ( ! is_array($credentials)) {
     1175                $credentials = array();
     1176        }
     1177
    10641178        $hostname = isset( $credentials['hostname'] ) ? $credentials['hostname'] : '';
    10651179        $username = isset( $credentials['username'] ) ? $credentials['username'] : '';
    10661180        $public_key = isset( $credentials['public_key'] ) ? $credentials['public_key'] : '';
     
    10681182        $port = isset( $credentials['port'] ) ? $credentials['port'] : '';
    10691183        $connection_type = isset( $credentials['connection_type'] ) ? $credentials['connection_type'] : '';
    10701184
     1185        if ( is_null( $extra_fields ) )
     1186                $extra_fields = array( 'version', 'locale' );
     1187
    10711188        if ( $error ) {
    10721189                $error_string = __('<strong>ERROR:</strong> There was an error connecting to the server, Please verify the settings are correct.');
    10731190                if ( is_wp_error($error) )
     
    11901307</div>
    11911308</form>
    11921309<?php
    1193         return false;
     1310
    11941311}
  • 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->assertInternalType( 'array', $credentials );
     16                $this->assertArrayHasKey( 'connection_type', $credentials );
     17                $this->assertEquals( 'direct', $credentials['connection_type'] );
     18
     19                // test SSH
     20                $credentials = get_filesystem_credentials('', 'ssh');
     21                $this->assertInternalType( 'array', $credentials );
     22
     23                $this->assertArrayHasKey( 'connection_type', $credentials );
     24                $this->assertArrayHasKey( 'private_key', $credentials );
     25                $this->assertArrayHasKey( 'public_key', $credentials );
     26                $this->assertArrayHasKey( 'hostname', $credentials );
     27                $this->assertArrayHasKey( 'username', $credentials );
     28                $this->assertArrayHasKey( 'password', $credentials );
     29
     30                $this->assertEquals( '', $credentials['private_key'] );
     31                $this->assertEquals( '', $credentials['public_key'] );
     32                $this->assertEquals( '', $credentials['hostname'] );
     33                $this->assertEquals( '', $credentials['username'] );
     34                $this->assertEquals( '', $credentials['password'] );
     35
     36                $_POST['hostname'] = rand_str();
     37                $_POST['private_key'] = rand_str();
     38                $_POST['public_key'] = rand_str();
     39                $_POST['username'] = rand_str();
     40                $_POST['password'] = rand_str();
     41
     42                $credentials = get_filesystem_credentials('', 'ftp');
     43
     44                $this->assertEquals( $_POST['private_key'], $credentials['private_key'] );
     45                $this->assertEquals( $_POST['public_key'], $credentials['public_key'] );
     46                $this->assertEquals( $_POST['hostname'], $credentials['hostname'] );
     47                $this->assertEquals( $_POST['username'], $credentials['username'] );
     48                $this->assertEquals( $_POST['password'], $credentials['password'] );
     49
     50                unset($_POST['hostname'], $_POST['private_key'], $_POST['public_key'], $_POST['username'], $_POST['password']);
     51
     52
     53                // test filter override:
     54                add_filter( 'request_filesystem_credentials', array('Tests_Filesystem_Credentials', '__filesystem_credentials'), 10, 3);
     55
     56                $credentials = get_filesystem_credentials('', 'ssh');
     57                $_credentials = Tests_Filesystem_Credentials::__filesystem_credentials('', '', 'ssh');
     58
     59                $this->assertEquals( $_credentials, $credentials);
     60
     61                // clean up filter
     62                remove_filter( 'request_filesystem_credentials', array('Tests_Filesystem_Credentials', '__filesystem_credentials'), 10, 3);
     63        }
     64
     65        /**
     66         *
     67         */
     68        function test_usable_filesystem_credentials() {
     69                $credentials = array('connection_type' => 'direct');
     70
     71                $this->assertTrue( usable_filesystem_credentials( $credentials ) );
     72
     73                // ssh: only one that needs public/private keys
     74                $credentials = get_filesystem_credentials( '', 'ssh' );
     75                $credentials['connection_type'] = 'ssh';
     76
     77                $this->assertFalse( usable_filesystem_credentials( $credentials ) );
     78
     79                $credentials['private_key'] = $credentials['public_key'] = 'a_value';
     80                $this->assertTrue( usable_filesystem_credentials( $credentials ) );
     81
     82                // ssh2
     83                $credentials['connection_type'] = 'ssh2';
     84                $this->assertFalse( usable_filesystem_credentials( $credentials ) );
     85
     86                $credentials = array(
     87                        'hostname' => 'a',
     88                        'username' => 'b',
     89                        'password' => 'c',
     90                        'connection_type' => 'ssh2'
     91                );
     92                $this->assertTrue( usable_filesystem_credentials( $credentials ) );
     93
     94
     95                // ftp
     96                $credentials = array(
     97                        'hostname' => '',
     98                        'username' => '',
     99                        'password' => '',
     100                        'connection_type' => 'ftp'
     101                );
     102                $this->assertFalse( usable_filesystem_credentials( $credentials ) );
     103
     104                $credentials = array(
     105                        'hostname' => 'a',
     106                        'username' => 'b',
     107                        'password' => 'c',
     108                        'connection_type' => 'ftp'
     109                );
     110                $this->assertTrue( usable_filesystem_credentials( $credentials ) );
     111        }
     112
     113        /** Helper functions */
     114        public static function __filesystem_credentials($empty, $form_url, $type) {
     115                switch ($type) {
     116                        case 'ssh':
     117                                $credentials = array(
     118                                        'connection_type' => 'ssh',
     119                                        'private_key' => 'b',
     120                                        'public_key' => 'c'
     121                                );
     122                                break;
     123                        default:
     124                                $credentials = array(
     125                                        'hostname' => 'a',
     126                                        'username' => 'b',
     127                                        'password' => 'c',
     128                                        'connection_type' => 'ftp'
     129                                );
     130                                break;
     131                }
     132
     133                return $credentials;
     134        }
     135}