New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved Joomla! extensions update handling of paid for extensions #2769

Merged
merged 6 commits into from Feb 22, 2014

Conversation

Projects
None yet
4 participants
@shumisha
Contributor

shumisha commented Jan 8, 2014

Hi,

In starting to use the paid-for extensions updater recently introduced in 3.x (tracker 32684, #2508), I came across some limitations that make it difficult for us to actually use it.
Therefore I'm proposing the following small contribution to make this feature a bit more universal.

What could be improved:

  • cannot use in J! 2.5 (w/o backporting, ie lots of work and maintenance)
  • pass all credentials into the url, which means credentials are stored in servers and proxies log files along the way, for all to view
  • not easily made dynamic: ie download credentials are "hardcoded" in the update sites DB table, which allow "replaying", ie: once somebody has sniffed credentials, they can use them at will, an unlimited number of times, and pass them along to anyone
  • cannot easily share credentials between extensions, ie one main component, controlling updates of various plugins or language files for instance

The proposal is simply to add firing an event at the right time and place, to allow an extension-supplied plugin to alter the url and/or headers on the fly as required by the extension supplier authorization mechanism.

Drawbacks:

  • need to add a plugin to respond to the event, easy for larger extension which may already have one or more plugins, more work for those who don't

Benefits:

  • simple, backward compatible change (4 lines added in one file)
  • same 4 lines and same plugin can be used the same on J! 2.5, providing Joomla update for paid extension on 2.5 as well
  • fairly simple plugin, I'll provide sample one
  • same plugin can control update for several extensions
  • can use urls var or headers to pass credentials, less data stored in log files in the later case
  • can use dynamic, time stamped requests to avoid replay

This PR is backward compatible and doesn't touch existing code, it only adds a bit more flexibility.

Also see tracker item: http://joomlacode.org/gf/project/joomla/tracker/?action=TrackerItemEdit&tracker_item_id=33096&start=0

@shumisha

This comment has been minimized.

Show comment
Hide comment
@shumisha

shumisha Jan 8, 2014

Contributor

Here is a sample plugin to respond the download event, and insert credentials in the download request. The plugin should be in the "installer" group, but any other plugin type is fine as long as you make sure the plugin is loaded when needed.

<?php
/**
 * @ant_title_ant@
 *
 * @author      @ant_author_ant@
 * @copyright   @ant_copyright_ant@
 * @package     @ant_package_ant@
 * @license     @ant_license_ant@
 * @version     @ant_version_ant@
 * @date        @ant_current_date_ant@
 */

defined('_JEXEC') or die;

/**
 * Handle commercial extension update authorization
 *
 * @package     MyPackage
 * @subpackage  MyPackage.update
 * @since       2.5
 */
class PlgInstallerSample extends JPlugin
{
    /**
     * @var    String  base update url, to decide whether to process the event or not
     * @since  2.5
     */
    private $baseUrl = 'http://update.example.com/subscribers/com_sample';

    /**
     * @var    String  your extension identifier, to retrieve its params
     * @since  2.5
     */
    private $extension = 'com_sample';

    /**
     * Handle adding credentials to package download request
     *
     * @param   string  $url        url from which package is going to be downloaded
     * @param   array   $headers    headers to be sent along the download request (key => value format)
     *
     * @return  boolean true        Always true, regardless of success
     *
     * @since   2.5
     */
    public function onInstallerBeforePackageDownload(&$url, &$headers)
    {
        // are we trying to update our extension?
        if (strpos($url, $this->baseUrl) !== 0)
        {
            return true;
        }

        // read credentials from extension params or any other source
        $credentials = $this->fetchCredentials($url, $headers);

        // bind credentials to request, either in the urls, or using headers
        // or a combination of both
        $this->bindCredentials($credentials, $url, $headers);

        return true;
    }

    /**
     * Retrieve user credentials, adjust as needed based on extension being processed
     *
     * @return mixed an array with credentials (access, secret)
     */
    private function fetchCredentials($url, $headers)
    {
        // fetch credentials from extension parameters, or
        // wherever you want to store them
        JLoader::import('joomla.application.component.helper');
        $component = JComponentHelper::getComponent($this->extension);
        $credentials = array('access' => $component->params->get('update_credentials_access', ''),
            'secret' => $component->params->get('update_credentials_secret', ''));
        return $credentials;
    }

    /**
     * Bind credentials to the download request. Can be done by adding them to 
     * the download url, or using headers, or any other method you like
     * As a sample, below is (close to) what we use, ie authorization headers, so that access and secrets are 
     * not stored in web servers and proxies log files
     *
     * @param   array   $credentials    whatever credentials were retrieved for the current user/website
     * @param   string  $url            url from which package is going to be downloaded
     * @param   array   $headers        headers to be sent along the download request (key => value format)
     * 
     * @return void
     */
    private function bindCredentials($credentials, &$url, &$headers)
    {
        $headers['X-download-auth-ts'] = time();
        $headers['X-download-auth-access'] = $credentials['access'];
        $headers['X-download-auth-token'] = sha1($headers['X-download-auth-ts'] . mt_rand() . $credentials['secret'] . $url);
        $headers['X-download-auth-sig'] = sha1(
            $credentials['access'] . $headers['X-download-auth-token'] . $credentials['secret'] . $headers['X-download-auth-ts'] . $this->extension);
    }
}
Contributor

shumisha commented Jan 8, 2014

Here is a sample plugin to respond the download event, and insert credentials in the download request. The plugin should be in the "installer" group, but any other plugin type is fine as long as you make sure the plugin is loaded when needed.

<?php
/**
 * @ant_title_ant@
 *
 * @author      @ant_author_ant@
 * @copyright   @ant_copyright_ant@
 * @package     @ant_package_ant@
 * @license     @ant_license_ant@
 * @version     @ant_version_ant@
 * @date        @ant_current_date_ant@
 */

defined('_JEXEC') or die;

/**
 * Handle commercial extension update authorization
 *
 * @package     MyPackage
 * @subpackage  MyPackage.update
 * @since       2.5
 */
class PlgInstallerSample extends JPlugin
{
    /**
     * @var    String  base update url, to decide whether to process the event or not
     * @since  2.5
     */
    private $baseUrl = 'http://update.example.com/subscribers/com_sample';

    /**
     * @var    String  your extension identifier, to retrieve its params
     * @since  2.5
     */
    private $extension = 'com_sample';

    /**
     * Handle adding credentials to package download request
     *
     * @param   string  $url        url from which package is going to be downloaded
     * @param   array   $headers    headers to be sent along the download request (key => value format)
     *
     * @return  boolean true        Always true, regardless of success
     *
     * @since   2.5
     */
    public function onInstallerBeforePackageDownload(&$url, &$headers)
    {
        // are we trying to update our extension?
        if (strpos($url, $this->baseUrl) !== 0)
        {
            return true;
        }

        // read credentials from extension params or any other source
        $credentials = $this->fetchCredentials($url, $headers);

        // bind credentials to request, either in the urls, or using headers
        // or a combination of both
        $this->bindCredentials($credentials, $url, $headers);

        return true;
    }

    /**
     * Retrieve user credentials, adjust as needed based on extension being processed
     *
     * @return mixed an array with credentials (access, secret)
     */
    private function fetchCredentials($url, $headers)
    {
        // fetch credentials from extension parameters, or
        // wherever you want to store them
        JLoader::import('joomla.application.component.helper');
        $component = JComponentHelper::getComponent($this->extension);
        $credentials = array('access' => $component->params->get('update_credentials_access', ''),
            'secret' => $component->params->get('update_credentials_secret', ''));
        return $credentials;
    }

    /**
     * Bind credentials to the download request. Can be done by adding them to 
     * the download url, or using headers, or any other method you like
     * As a sample, below is (close to) what we use, ie authorization headers, so that access and secrets are 
     * not stored in web servers and proxies log files
     *
     * @param   array   $credentials    whatever credentials were retrieved for the current user/website
     * @param   string  $url            url from which package is going to be downloaded
     * @param   array   $headers        headers to be sent along the download request (key => value format)
     * 
     * @return void
     */
    private function bindCredentials($credentials, &$url, &$headers)
    {
        $headers['X-download-auth-ts'] = time();
        $headers['X-download-auth-access'] = $credentials['access'];
        $headers['X-download-auth-token'] = sha1($headers['X-download-auth-ts'] . mt_rand() . $credentials['secret'] . $url);
        $headers['X-download-auth-sig'] = sha1(
            $credentials['access'] . $headers['X-download-auth-token'] . $credentials['secret'] . $headers['X-download-auth-ts'] . $this->extension);
    }
}
@spignataro

This comment has been minimized.

Show comment
Hide comment
@spignataro

spignataro Jan 16, 2014

Contributor

I tested this pull request and it doesn't seem to effect anything. My component still updated properly.

However I did not create a plugin to test the additional install plugin trigger.

Contributor

spignataro commented Jan 16, 2014

I tested this pull request and it doesn't seem to effect anything. My component still updated properly.

However I did not create a plugin to test the additional install plugin trigger.

@shumisha

This comment has been minimized.

Show comment
Hide comment
@shumisha

shumisha Jan 16, 2014

Contributor

Steven, FYI, I have attached a plugin implementing Akeeba Release system to the tracker item: http://joomlacode.org/gf/project/joomla/tracker/?action=TrackerItemEdit&tracker_item_id=33133&start=0

(same plugin works both on 2.x and 3.x)

Contributor

shumisha commented Jan 16, 2014

Steven, FYI, I have attached a plugin implementing Akeeba Release system to the tracker item: http://joomlacode.org/gf/project/joomla/tracker/?action=TrackerItemEdit&tracker_item_id=33133&start=0

(same plugin works both on 2.x and 3.x)

mbabker added a commit that referenced this pull request Feb 22, 2014

Merge pull request #2769 from shumisha/auth_update
Improved Joomla! extensions update handling of paid for extensions

@mbabker mbabker merged commit 4d1d991 into joomla:staging Feb 22, 2014

1 check passed

default The Travis CI build passed
Details

Bakual added a commit to Bakual/joomla-cms that referenced this pull request May 12, 2014

Merge pull request #2769 from shumisha/auth_update
Improved Joomla! extensions update handling of paid for extensions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment