This issue expands on the issue at http://drupal.org/node/881752 with a focus on whether an "empty cart" confirmation is necessary in ALL use cases. To summarize the issue, the security concern with the original cart links empty cart function is that rogue sites can empty the user cart on your site, when the user browses their site, by posting your Cart Links on their site (even with a sneaky technique like Only local images are allowed..

I'm interested in feedback surrounding whether a security compromise could be reached on this issue between the concern about rogue sites and the use cases where the rogue sites, in the opinion of the site owner, are not as problematic as the decreased usability of the "empty cart" confirmation. Site owners who are not concerned can obviously pick up the old Cart Links code from 6.x.2.3, but I for one would like to remain in the fold of the ever watchful security team coders for whom I'm extremely grateful.

My use case is: I sell only an annual subscription, or a monthly subscription, and I have discounted versions of each, for a total of four products. A user will only ever want one. If a user's cart gets emptied, it's not a problem at all (not even a little bit), since I don't use the cart. The only way to get to the checkout screen is by clicking on the cart link, and since every product is mutually exclusive this cart link also emptys the cart. For sites where every product is mutually exclusive, the balance on this issue falls decidedly on the side of allowing Cart Links to empty the cart without a confirmation screen.

On issues like this where a large number of site owners disagree with the decision of the security team, site owners are compelled to use old, unsupported code to get the functionality that is appropriate for their particular use case. Compelling people to use unsupported code (which I don't, by the way, but it's sure tempting...) is an additional concern the security team need to consider.

I suggest that the empty cart confirmation page be made optional via a checkbox at /admin/store/settings/cart_links. It should default to "on", with big red letters advising site owners not to disable it. Which I would, since that's what works for my site.

Comments

shaundychko’s picture

Ahhh, should have hit preview... the "sneaky technique", which looks especially sneaky in the post above since it's invisible should read <img src="http://www.example.com/e-ihahaha" />

shaundychko’s picture

Title: Use Cart Links empty cart confirmation only if products are not mutually exclusive » Skip Cart Links empty cart confirmation if products are mutually exclusive
longwave’s picture

Any further changes to this will need input from the security team.

Adding a checkbox in the settings page may be viable, or perhaps we could add a variable that has no corresponding UI, so you could set something like $conf['uc_cart_links_skip_confirm'] = TRUE in settings.php as the only way to enable this "feature".

RKopacz’s picture

Hi, slightly off subject, but I am new to Ubercart and would like to know how you can set up products in a cart to make them mutually exclusive. Does anyone have a link to documentation or a tutorial? Much thanks.

shaundychko’s picture

@RKopacz, the only way I'm aware of doing this is via Cart Links. You can empty the cart and add a product to it using a single URL. Enable Cart Links from your modules page and visit /admin/store/help/cart_links on your own site for instructions on creating these links. Note that currently you'll need the Cart Links module from 6.x-2.x-dev in order for the empty cart function to work. ("empty cart" doesn't work with 6.x-2.4 even though documentation suggests it will).

RKopacz’s picture

Hey, thanks very much for the quick response. I will try out your suggestion, let you know what I come up with.

r

jrowny’s picture

I can create a patch to add a setting for enabling/disabling the confirmation screen. If there's adequate warning and the warning is turned ON by default, I don't see why the security team wouldn't approve it.

shaundychko’s picture

Hey, you should check out http://drupal.org/node/881752 before spending too much time on that.

jrowny’s picture

The result of that thread still has a "confirmation" page. My patch would be to make the confirmation page an option set in store configutation->cart links.

The patch wouldn't be very long... there's a single line of code which checks for "e" and returns the confirm form. I'd simply add to that the check for the setting.

longwave’s picture

Before embarking on this, I suggest you contact security@drupal.org, or perhaps webchick or greggles directly as they were the ones involved in the previous issue - no further patches will be accepted for this without their blessing.

jrowny’s picture

talked to Webchick in IRC and I agreed with her that my use case of cart links is probably an abuse of cart links and I should probably just write a hook to handle my product correctly.

tr’s picture

Version: 6.x-2.x-dev » 7.x-3.x-dev

New features should go into 7.x-3.x first at this point.

thehideki’s picture

Moved here from : http://drupal.org/node/1091122

I needed this also, decided on forking the cart_links module to use a hash created from the user & product data, and a check that the hash matches. If the hash value matches, the confirmation is skipped.

Maybe some kind of optional hash parameter could be added to the url check on 7.x? It could be done using an admin settings field with token support. Then one could create a link (with views or your preferred method), and the cart_links function could check the parameter, and bypass the validation if it matches.

Here's what i did to create this functionality on the D6 version. Currently works only on one product per link.

D6 version

--

FILE: uc_cart_links_pages:

rewrote the first function, and added another below it:

function uc_cart_links_form($form_state, $arg1) {
  // Fail if the link is restricted.
  $data = variable_get('uc_cart_links_restrictions', '');
  if (!empty($data)) {
    $restrictions = explode("\n", variable_get('uc_cart_links_restrictions', ''));
    $restrictions = array_map('trim', $restrictions);

    if (!empty($restrictions) && !in_array($arg1, $restrictions)) {
      $url = variable_get('uc_cart_links_invalid_page', '');
      if (empty($url)) {
        $url = '<front>';
      }
      unset($_REQUEST['destination']);
      drupal_goto($url);
    }
  }

  // Confirm with the user if the form contains a destructive action.
  // Skip confirmation if hash check is passed
  $items = uc_cart_get_contents();
  $emptycart = FALSE;
  $hashcheck = FALSE;
  $hash = null;
  
  if (variable_get('uc_cart_links_empty', TRUE) && !empty($items)) {
    $actions = explode('-', urldecode($arg1));
    foreach ($actions as $action) {
      switch (drupal_substr($action, 0, 1)) {
        case 'e':
        case 'E':
          $emptycart = TRUE;
        break;
        
        // We need the product node id for token checks relating to the product
        case 'p':
        case 'P':
          // @todo: there has to be a better way of doing this part
          $parts = explode('_', $action);
          foreach ($parts as $part) {
            switch (drupal_substr($part, 0, 1)) { // Parse the product action to get the product nid
              case 'p':
              case 'P':
                $pnid = intval(drupal_substr($part, 1));
              break;
            }
          }
        break;
        
        case 'h':
        case 'H':
          $hash = drupal_substr($action, 1);
        break;
      }
    }
  }
  
  $compare = variable_get('uc_cart_links_hash', '');
  if (!empty($compare)) {
    $hashcheck = _uc_cart_links_hashcheck($hash,$compare,$pnid);
  }
  
  if (!$hashcheck && $emptycart = TRUE) {
    return confirm_form(array(), t('The current contents of your shopping cart will be lost. Are you sure you want to continue?'), variable_get('uc_cart_links_invalid_page', ''));
  }
  // No destructive actions, so process the link immediately.
  uc_cart_links_process($arg1);
}


/**
 * Hash checker helper function
 */
function _uc_cart_links_hashcheck($tocheck,$productid==null) {
  
  $passed = FALSE;
  
  if (!empty($hash) && !empty($compare)) {
    
    global $user;
    // do token replacement
    $compare = token_replace($compare, 'global');
    $compare = token_replace($compare, 'user', $user);
    if ($productid) {
      $product = node_load($productid);
      $compare = token_replace($compare, 'node', $product);
    }    
    // compare
    if (md5($compare)==$hash) {
      $passed = TRUE;
    }
  }
  return $passed;
}

FILE: uc_carts_links.admin.inc

Added these to the settings form

$form['uc_cart_links_hash'] = array(
      '#type' => 'textfield',
      '#title' => t('Use token hash check'),
      '#default_value' => variable_get('uc_cart_links_hash', ''),
      '#description' => t('If this field is filled and a link contains an invalid hash value, the user is redirected to the confirmation page. Otherwise confirmation is skipped and the user is redirected straight to the checkout page. Uses MD5 hashing. Use token values below to create the hash.'),
  );
  
  if (module_exists('Token')) {
    
    $form['uc_cart_links_hash_token'] = array(
      '#title' => t('Replacement patterns'),
      '#type' => 'fieldset',
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
      '#description' => t('Prefer raw-text replacements for text to avoid problems with HTML entities!'),
    );
    $form['uc_cart_links_hash_token']['help'] = array(
      '#value' => theme('token_help',array('global','user','node')), // these are supported for now
    );
  }

tr’s picture

Please make a patch and post it here as a file attachment.

thehideki’s picture

Patch as requested. (this patch is for 6.x-2.x, as i have not used 7.x yet)

tr’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, ubercart-cart_links_hash_check-1091166-13.patch, failed testing.

longwave’s picture

Version: 7.x-3.x-dev » 6.x-2.x-dev
Status: Needs work » Needs review
tr’s picture

Status: Needs review » Needs work

The last submitted patch, ubercart-cart_links_hash_check-1091166-13.patch, failed testing.

tr’s picture

Status: Needs work » Needs review

The Cart Links module has very good test coverage right now. I don't think new features should go in unless we have new tests to accompany them. So I think this patch should include new test cases to verify this new feature is working and to ensure it stays working.

Also, the patch needs to be fixed to conform with Drupal coding standards. See http://drupal.org/coding-standards. You can use the Coder module to help you review your code. Coder catches a lot, but not everything, so it's worthwhile to become familiar with the standards.

tr’s picture

Status: Needs review » Needs work

Crosspost - patch failed testing, should still be needs work.

mrfelton’s picture

Needed a quick fix, no time to debug the patch in #15 which causes fatal errors. So here is a oneliner that at least allows us to move forward for now.

jimurl’s picture

I needed the ability for an administrator to override the 'Do you really want to empty your cart? 'confirmation page. The patch in #15 introduces the hash to deal with this, but isn't compliant. The patch submitted by Mr. Felton removes the confirmation page altogether-which, it sounds, won't fly with the security team. Until the hashing system for cart links can be completed, this patch gives the site administrator the ability to disable this 'empty cart verify' feature via a checkbox on the cart links settings page, admin/store/settings/cart_links .

tr’s picture

Status: Needs work » Needs review

You have to set the issue status to "needs review" to get the testbot to try out the patch.

Status: Needs review » Needs work

The last submitted patch, ubercart-uc_cart_links-skip_emptycart_verify-1091166-24.patch, failed testing.

jimurl’s picture

Title: Skip Cart Links empty cart confirmation if products are mutually exclusive » The patch failed, perhaps due to the name
Status: Needs work » Needs review
StatusFileSize
new1.46 KB

I notice now there are also some truncated lines, trying again.

longwave’s picture

Title: The patch failed, perhaps due to the name » Skip Cart Links empty cart confirmation if products are mutually exclusive

Fixing issue title

polmaresma’s picture

#27 worked for me.

Thank's!

tr’s picture

Version: 6.x-2.x-dev » 7.x-3.x-dev
Status: Needs review » Needs work

The patches in #23, #24, and #27 can't be put into Ubercart because they circumvent the confirmation that is required by the Drupal security team. They also don't address the specific topic of this issue.

The patch in #15 has potential to solve this issue, but the points I raised in #21 haven't been addressed and no one has tested it.

Moving back to "needs work" for these reasons. Also moving to 7.x-3.x because that is where new features should be implemented first.

julian.montagna’s picture

Hi,

I've solved this in two simple steps:

1) Editing a custom module adding a new entry in hook_menu like:

  $items['store/add/%'] = array(
    'title' => 'Add to cart',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('mycustommodule_links_form', 2),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
    'file' => 'uc_cart_links.pages.inc',
    'file path' => drupal_get_path('module', 'uc_cart_links'),
  );

2) Copy and modify uc_cart_links_form code from uc_cart_links.pages.inc like:

/**
 * Preprocess a cart link and confirm with the user if a destructive action
 * is included.
 *
 * @see uc_cart_links_form_submit()
 */
function mycustommodule_links_form($form_state, $arg1) {
  // Fail if the link is restricted.
  $data = variable_get('uc_cart_links_restrictions', '');
  if (!empty($data)) {
    $restrictions = explode("\n", variable_get('uc_cart_links_restrictions', ''));
    $restrictions = array_map('trim', $restrictions);

    if (!empty($restrictions) && !in_array($arg1, $restrictions)) {
      $url = variable_get('uc_cart_links_invalid_page', '');
      if (empty($url)) {
        $url = '<front>';
      }
      unset($_REQUEST['destination']);
      drupal_goto($url);
    }
  }

  // Comment out, remove or add new custom behavior here.
  // Confirm with the user if the form contains a destructive action.
  /*
  $items = uc_cart_get_contents();
  if (variable_get('uc_cart_links_empty', TRUE) && !empty($items)) {
    $actions = explode('-', urldecode($arg1));
    foreach ($actions as $action) {
      $action = drupal_substr($action, 0, 1);
      if ($action == 'e' || $action == 'E') {
        return confirm_form(array(), t('The current contents of your shopping cart will be lost. Are you sure you want to continue?'), variable_get('uc_cart_links_invalid_page', ''));
      }
    }
  }
  */

  // No destructive actions, so process the link immediately.
  uc_cart_links_process($arg1);
}

I really don't know if this is the best approach but since is a very particular business need, this way you can get what you need without modifying the module, the core or even better without disturbing security@drupal.org guys. :P

Hope this helps somebody.
Cheers,

oliverpolden’s picture

Don't do this, it is a hack which you shouldn't do because it will get wiped out if you upgrade the module but if you want the quickest way round this then in uc_cart_links.pages.inc find line 34:
Insert a new line after $items = uc_cart_get_contents();
On the new line insert $items = array();

You could also just edit the first line or comment it out but this way you can revert easily.

haysuess’s picture

Just throwing my (and all my clients) opinion(s) into the mix. This is something I have to patch with EVERY single site I developer, and it got old 10 sites ago.

Nobody I've developed for cares if another site can create cart links that can potentially empty a cart on their site. The extra warning step is alarming to buyers and kills business that people are trying to build themselves, directly on their OWN site.

petarb’s picture

I echo haysuess's comments.

This is the key takeout:

The extra warning step is alarming to buyers and kills business that people are trying to build themselves, directly on their OWN site.

However, thanks to oliverpolden's suggested hack, everyone's happy with the outcome, it works perfectly. I think the maintainers of the module (who do a fantastic job by the way) should consider the ability for the admins to turn off this business-killing 'feature' on a per-site basis.

jimurl’s picture

I think the maintainers of the module (who do a fantastic job by the way) should consider the ability for the admins to turn off this business-killing 'feature' on a per-site basis.

That is what patch #27 does.

tr’s picture

Status: Needs work » Closed (won't fix)

Let me reiterate what was said in #3 and #10 and #30; if you want to be able to turn off the Cart Links confirmation, then you will have to contact the security team and work with THEM to get approval of your proposed change. No patch for this issue will be committed without approval of the security team.

In fact, I'm going to just go ahead and close this issue as it is getting nowhere and hasn't produced a viable patch. Feel free to reopen (or better yet start a new thread) IF you have a patch and have discussed your approach with the security team AND they approve of the proposed solution.