YahnisElsts / plugin-update-checker

A custom update checker for WordPress plugins. Useful if you don't want to host your project in the official WP repository, but would still like it to support automatic updates. Despite the name, it also works with themes.
MIT License
2.26k stars 410 forks source link

Use .php insted of .json file? #424

Open JackobStuard opened 3 years ago

JackobStuard commented 3 years ago

Hi

I am making php script that whitelist domains for update checker. But I see that "plugin-update-checker" only allow .json format? Can I use .php file insted of .json file? I want to make on .php when some domain is whitelisted then will show json result in .php file? I test but I dont see that is working, for me only work when I add .json file :(

Here is my code:

<?php

$allowedDomains = array('www.example1.com', 'www.example2.com', 'example3.com');
$referer = $_SERVER['HTTP_REFERER'];
$domain = parse_url($referer);
if(in_array( $domain['host'], $allowedDomains)) {

$inputArray =  array (
                'name' => 'TEST',
                'version' => '1.0.0',
                'download_url' => 'http://example.com/test.zip',
                'sections' => 
                array (
                  'description' => '',
    ),
);

$encodedJSON = json_encode($inputArray, JSON_PRETTY_PRINT);
print($encodedJSON);

} else {
    echo "you are not allowed to post at this page";
    exit();
}

?>
YahnisElsts commented 3 years ago

Sure, you can write a PHP script to return a JSON response. As long as the script output is correctly formatted and has the required fields, it should work as well as a static JSON file.

One issue that I see in your code is that it relies on the "referer" header which probably won't be set in this case. The update checker doesn't add a referer header when making requests and I don't think WordPress normally adds it either. So this code probably returns the "not allowed" message every time.

If you want to know which domain sent the request, you'll need to do something else. For example, you could add the domain name to the update URL as a query parameter. It's not exactly safe since another developer could easily edit that query parameter, but it might still serve as a very basic verification mechanism.

JackobStuard commented 3 years ago

@YahnisElsts Interesting that this script again is not working :(

Pls see now is without "referer" header:

<?php
$allowedDomains = [
    'https://www.example1.com', 
    'http://www.example2.com', 
    'https://example3.com',
];

$message = "you are not allowed to post at this page";
$url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";

$domain = parse_url($url, PHP_URL_HOST);

if(
    in_array(
        $domain, 
        $allowedDomains
    )
) 
{

    $inputArray =  [
        'name' => 'TEST',
        'version' => '1.0.0',
        'download_url' => 'http://example.com',
        'sections' => 
        [
          'description' => '',
        ],
    ];

    $encodedJSON = json_encode($inputArray, JSON_PRETTY_PRINT);
    print($encodedJSON);

}else{
    die($message); //Stop running the script
}

?>
YahnisElsts commented 3 years ago

$_SERVER['HTTP_HOST'] typically contains the domain name of the site on which the script is running. It is not the domain name of the site that's sending the request. For example, if you run your script on "example.com" then $domain will always equal example.com, regardless of which site is actually sending the request.

Perhaps you could try running the script without any domain-related conditions just to verify that it works and that it outputs valid JSON?

JackobStuard commented 3 years ago

@YahnisElsts Yes, I try to make .php file only with JSON output, that works ok But I still want to limit by domains, I don't want to user use my plugin on multi domains if is my plugin only for one domain, I want to add in PHP script manually domain and if script find that domain name who is requesting access to show json data. Can you try to give me some examples in code what can I do to working with plugin-update-checker? I am not an expert in coding, so any help with code that will help me a lot...

YahnisElsts commented 3 years ago

Unfortunately, I don't have a simple solution that I could share. I use a system based on license keys for one of my own plugins, but it's too complex to be useful as an example. It's several thousand lines of code and it was built for my own use, so it wouldn't be easy to adapt for a different plugin. The general idea is described here.

JackobStuard commented 3 years ago

@YahnisElsts Something with my script is not possible to do?

YahnisElsts commented 3 years ago

What do you mean? If you're asking if it's possible to get the domain name of the site that's sending the request, you can sometimes do that by extracting the site URL from the User-Agent header. WordPress will set it to something like this by default:

WordPress/5.7; https://example.com/

You could user a regex or some string manipulation to get the URL from the header.

Of course, this is not a safe solution because anyone could replace the header value with something else. Some plugins automatically remove the site URL from the User-Agent header for security reasons.

JackobStuard commented 3 years ago

In plugin if i put:

require 'plugin-update-checker/plugin-update-checker.php';
$myUpdateChecker = Puc_v4_Factory::buildUpdateChecker(
    'http://example.com/test.php',
    __FILE__, //Full path to the main plugin file or functions.php.
    'test'
);

And in that test.php file apply your solution to my previously script

<?php
$allowedDomains = [
    'https://www.example1.com', 
    'http://www.example2.com', 
    'https://example3.com',
];

$message = "you are not allowed to post at this page";
$url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";

$domain = parse_url($url, PHP_URL_HOST);

if(
    in_array(
        $domain, 
        $allowedDomains
    )
) 
{

    $inputArray =  [
        'name' => 'TEST',
        'version' => '1.0.0',
        'download_url' => 'http://example.com',
        'sections' => 
        [
          'description' => '',
        ],
    ];

    $encodedJSON = json_encode($inputArray, JSON_PRETTY_PRINT);
    print($encodedJSON);

}else{
    die($message); //Stop running the script
}

?>

Why you think that they can easily change header? if my test.php stay on my server? you mean maybe in wp they can easy change or?

YahnisElsts commented 3 years ago

That script is still looking for the domain name in the wrong place (HTTP_HOST).

Why you think that they can easily change header? if my test.php stay on my server? you mean maybe in wp they can easy change or?

I mean that the user could add some code to their site (e.g. in functions.php) to change the header. Of course, the user would have to be familiar with PHP and WordPress development to do that, which might be uncommon. But replacing the user agent is pretty straightforward:

add_filter('http_request_args', function($args) {
    $args['user-agent'] = 'WordPress/6.6.6; http://evil.com/';
    return $args;
});
JackobStuard commented 3 years ago

Intersting idea, If I put your code:

add_filter('http_request_args', function($args) {
    $args['user-agent'] = 'WordPress/6.6.6; http://evil.com/';
    return $args;
});

in my main plugin.php file can that works as well? or need to be in functions.php? Also I dont need to change anything in my code test.php (located on my server)? just need to add your line of code to my main plugin to see HTTP_HOST ?

JackobStuard commented 3 years ago

That will maybe now work because after updating I will lose that "add_filter" in plugin I just need to force that header to show on request to my server test.php... Or add your code:

add_filter('http_request_args', function($args) { $args['user-agent'] = 'WordPress/6.6.6; http://evil.com/'; return $args; });

but here with url: 'http://evil.com/' to put to detect site url

YahnisElsts commented 3 years ago

You could theoretically put it in your plugin file, yes. It still wouldn't be a reliable way to add the site URL to the header since another script could easily use the same filter to overwrite the header. However, if your users are not developers, you might not need to worry about that.

Note that this has nothing to do with $_SERVER['HTTP_HOST'].

JackobStuard commented 3 years ago

Yes I know that, but they can unzip my plugin and zip to another wp? and they will get all updates notifications... So I think that will be awesome to found some way to restrict test.php file on my server. With tokens is possible, but I will be happy to restrict by domain who is sending request to my server test.php

YahnisElsts commented 3 years ago

Sure. An imperfect solution is better than nothing.

JackobStuard commented 3 years ago

@YahnisElsts Yes I know, I only want to restrict to someone (non-coder) to cant just zip and unzip my plugin... I will like to implement your code

add_filter('http_request_args', function($args) {
$args['user-agent'] = 'WordPress/6.6.6; http://evil.com/';
return $args;
});

To summary today's issue, can you explain in a bit more how to implement it? I am not sure in today's conversation if you say that is not possible or is possible to try like that? you say that if other plugins use that code that can be problematic?

YahnisElsts commented 3 years ago

My opinion is that it is possible to add that code to your plugin, but it's probably not a useful thing to do.

To put it more strongly: Ignore my code. The code I posted was just an example to illustrate how someone could put a fake URL in the User-Agent header. I'm not sure why you want to put that code in your plugin. You don't need it to add the site URL to the User-Agent: WordPress already does that automatically. Other plugins can change that, but you can't reliably prevent them from changing it.

ddur commented 1 year ago

@JackobStuard

Why don't you try using https://github.com/YahnisElsts/wp-update-server?