Question

I have a PHP application where I would like to certain objects to persist in the following manner:

  1. The object must not exist in the $_SESSION. Separate web browser windows must control separate instances of the object.
  2. The end-user must not be able to modify the object by changing the content of the $_REQUEST variable by hand (if this happens the request should be treated as corrupted).

Is there a best-practices / proper way to do this? With PHP becoming more and more object oriented, I fear that I am reinventing a wheel.

The grand purpose of this code is to allow the creation and manipulation of complex objects without using a database until they are to be committed, then I will use a proper transaction to commit them to the database in full. I want to make it so that my database contains only the complete invoice, or no invoice at all.

My current method is as follows:

<?php

include('encrypt.php');
include('invoice.class.php');

if(isset($_REQUEST['invoice']))
{
    $invoice = unserialize(decrypt(base64_decode($_REQUEST['invoice'])));
    if(!($invoice instanceOf invoice)) throw new exception('Something bad happened');
}
else
{
    // Some pages throw an exception if the $_REQUEST doesn't exist.
    $invoice = new invoice();
}

if(isset($_REQUEST['action']) && $_REQUEST['action'] == 'addLine')
{
    $invoice->addLine(new invoiceLine($_REQUEST['description'], $_REQUEST['qty'], $_REQUEST['unitprice']);
}

?>
<form action="index.php" method="post">
<input type="text" name="qty" />
...
<input type="hidden" name="invoice" value="<?php echo(base64_encode(encrypt(serialize($invoice)))); ?>" />
</form>
Was it helpful?

Solution

You could also save state on the client, without cookies, using a simple hidden form input. As long as the the data (probably a serialized blob) is encrypted and signed, the user can't modify it without breaking their session.

Steve Gibson uses this method for his custom e-commerce system. While his code isn't open source, he thoroughly explains ways to save state without storing sensitive data on the server or requiring cookie support in Security Now Episode #109, "GRC's eCommerce System".

OTHER TIPS

Here's a trick: put it in a cookie!

Neat algorithm:

$data = serialize($object); $time = time(); $signature = sha1($serverSideSecret . $time . $data); $cookie = base64("$signature-$time-$data");

The benefit is that you

a) can expire the cookie when you want because you are using the timestamp as part of the signature hash.

b) can verify that the data hasn't been modified on the client side, because you can recreate the hash from the data segment in the cookie.

Also, you don't have to store the entire object in the cookie if it will be too big. Just store the data you need on the server and use the data in the cookie as a key.

I can't take credit for the algorithm, I learned it from Cal Henderson, of Flickr fame.

Edit: If you find using cookies too complicated, just forget about them and store the data that would have gone in the cookie in a hidden form field.

What I would do is store a cryptographic key (not the whole structure, like your example is) in a hidden form variable. This key is an index to a uncreated_invoices table where you store incomplete invoices.

Between pages, you update the data in the uncreated_invoices table, and when they're done, pull it out and commit it. If you put the username into the uncreate_invoices table, this will also let them pick up where they left off (not sure if thats a valid use case). It'd probably be a good idea to put the username in it anyways so people can't try to hijack other people's invoices.

You can probably get away with storing serialized data in the uncreated_invoices table, if you wanted. Personally, i'd normalize it a bit (how much depends on your schema) so you can easily add/remove individual pieces.

Edit: I forgot to mention that you should clean the uncreated_invoices table periodically so it doesn't fill up with stale invoices.

Couldn't you store the data in a array within $_SESSION, and then have a unique id for each window. If each window has unique id you can pass the id around as part of your forms. Store/retrieve the data in the session or database based on the window id.

So something like this? $_SESSION['data'][$windowid]->$objectname

If you can't use SESSION then you must persist the data yourself. You must place the data somewhere will it will persist, such as a database or some other file. Its the only way, besides SESSION to persit data.

I take that back, you can send the data in each HTML page and use it locally. Every page that accepts the data must create HTML/Javascript that continues the data sequence.

If you store things on the client side they can be modified. Is there a specific reason you don't want to store the objects in a session? If a storing the object actually isn't viable you'll need to persist it somewhere else on the server.

That's fine, they'll be able to add all the items they want. The problem in your code is that you're taking the item descriptor, quantity, AND PRICE from the request, the price really should be looked up in the background, otherwise, a user could hand craft their price. Heck, negative value items, and you get stuff for free.

You want to store something. That's usually done in databases. How do you know the price of something without looking it up in a database? So use the same database to store information about the current user/session.

You could use the magic methods __sleep() and __wakeup()

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top