NetTalk Central

Author Topic: Row ID's  (Read 12425 times)

Bruce

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 11250
    • View Profile
Row ID's
« on: May 03, 2011, 05:54:24 AM »
Robert reminded me today of a question that's come up before. It seemed of general interest so I'll post the question here, with the answer. Please feel free to post follow-up questions on this thread below.

Required

Much of the information below has been true for quite some time, however the section on creating a direct link to a form requires version 5.24 or later.

Question

I am moving from a Frames based site to a non-Frames based site. Some of my menu items call a form directly, as per FAQ W4. So I'm seeing the Row Id passed as part of the URL. This is especially true when I go directly to a form, without going via a browse. Is there some way of hiding, or encrypting this id?

Background

In a browse, or a form, some way of identifying the row, or record is required so that when you press a button, or save a form, it knows which bit of data is being used. If this identifier is a simple integer then it's possible to "guess" other possible correct values of the integer, and thus edit records which normally you wouldn't have access to.

In NetTalk 4 the row identifier is the component of the unique key. This is one of the reason I've recommended GUID style ID's rather than auto-incrementing integers for ID's. (And GUID style Id's are still recommended in NetTalk 5, although perhaps not quite as important).

In NT5 a "hash-row-id" system is used.  When a row id is required, an entry in an internal queue is created. In this queue the actual row id is stored, and also a random "hash" value - typically an 8 character random string. When the row id is passed back from the browser, it is the hash-value which comes in - this is automatically translated into the real value and the code progresses as if the real value was entered.

A hash value is created by calling

Code: [Select]
p_web.AddBrowseValue (string proc, string filename, key filekey, <field1Value>, <field2Value>, <field3Value>, <field4Value>, <field5Value>)

If the Fields are omitted, then the value in the current record is used. For example, let's say a record in the customer table is loaded. And the unique key for the customer table is cus:idkey. Then the call to get the hash would be;

Code: [Select]
str   string(Net:HashSize)
  code
  str = p_web.AddBrowseValue('browsecustomers','customers',cus:idkey)

On the other hand if the customer record was not loaded, but you wanted to get a hash for where cus:id = 2 then the code might look like this;

Code: [Select]
str   string(Net:HashSize)
  code
  str = p_web.AddBrowseValue('browsecustomers','customers',cus:idkey,2)

As you can see from the prototype, the method allows for the key to have up to five components.

Note that the "customers" parameter is in quotes - it's the _name_ of the table the procedure needs, whereas the key parameter has no quotes, it's the actual unique key that is required. The procedure name is an internal optimizer.

If you click on a browse row you'll see this identifier is sent to the server as

Code: [Select]
_bidv_=something.

Internally this is translated using the queue, and the actual Row Id values are stored in the session queue.
If you right-click and view-source of a browse, you'll see the values set for each row, in html, like this;

Code: [Select]
<tr data-nt-id="rcEOaCZA">

[aside: this technique is very compact, and one reason NT5 pages are smaller, and hence faster, than NT4 pages]

If the CHANGE button under the browse is clicked, then the url to the form looks like this;

Code: [Select]
http://127.0.0.1:88/UpdateCustomers

The Post Data contains the ID, in _bidv_ format - ie

Code: [Select]
_bidv_=9FsBSx2l

Thus the _bidv_ system is compact, secure and largely invisible to the programmer.

Now to the Form. When the form is called a FORMSTATE is created. This is similar to the hash in some ways, but has some important (internal) differences in other ways. If you do a right-click / view-source on a web page then you'll see the FormState as a hidden form field;

Code: [Select]
<input type="hidden" name="FormState" id="FormState" value="QFODAJKCSD"></input>
You may notice that in many cases it's he only hidden field on the form, replacing the 3 or 4 hidden fields required by NT4. Again the text in the form state is meaningless - it's used only as an index into the queue on the server. Changing the FormState has no ability to change other records, because only records in the FormSettings queue can be changed by the form.  Because the actual record id is stored on the server, and is not sent by the browser, it's impossible for the row id to be changed.

Answer

Using the p_web.AddBrowseValue method, a URL can be constructed containing a unique hash value. The URL will look something like this;

Code: [Select]
'SomeForm?change_btn=change&_bidv_=' & p_web.AddBrowseValue('pageheadertag','mailboxes',MAI:PrimaryKey,2)

In the above example 'pageheadertag' is the name of the procedure, 'mailboxes is the name of the file', mai:primaryKey is the key, and 2 is the value to be used in the key (in other words, mai:MailboxNumber=2).

Where a Procedure and parameters can be specified separately the procedure would be set to SomeForm and the parameters set to
Code: [Select]
'change_btn=change&_bidv_=' & p_web.AddBrowseValue('pageheadertag','mailboxes',MAI:PrimaryKey,2)

Example

As from build 5.24 I have updated example 24 (FormToForm) with two examples of this approach.
First, a menu item called "Direct!" has been added. This calls the form directly, with an ID value of 2.
Secondly, in the URL On Save, for the FirstForm is;
Code: [Select]
'SecondForm?change_btn=change&_bidv_=' & p_web.AddBrowseValue('firstform','mailboxes',MAI:PrimaryKey)

As you can see, in this URL the value is left off, so the current record value is used.

Where to From Here

At the moment (at at version 5.24 and earlier) the old system, as documented in FAQ W4 is still valid, and still works. This is necessary for a period at least to preserve backward compatibility. However if the ID of a record is known then this can be used to carefully construct a POST which could alter the record. So in a future build this method will be deprecated completely. therefore it is recommended that any "direct links" of the type created in W4 be converted to the new style at the earliest opportunity.

Using non-sequential-id fields can also be useful in making unknown Id's harder to "guess" - however I understand this is often not a choice you may be able to implement at this stage.

I expect this post is at least slightly confusing, so please post questions below if any of it is unclear. I will edit this post as required.

Cheers
Bruce


« Last Edit: May 03, 2011, 07:44:21 AM by Bruce »

bshields

  • Sr. Member
  • ****
  • Posts: 392
    • View Profile
    • Inhabit
    • Email
Re: Row ID's
« Reply #1 on: May 03, 2011, 05:02:38 PM »
Hi Bruce,

I noticed this recently. Its a top solution and I have been updating my code.

You saved me the hassle of obfuscating the IDs within GET and POST to avoid malice.

A competitor of mine (using PHP) failed to do this. He also had a demo account which was on the same system as real customers. It would have been a trivial task to delete all his data (I resisted the temptation).

Regards
Bill

Bruce

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 11250
    • View Profile
Re: Row ID's
« Reply #2 on: May 03, 2011, 11:07:02 PM »
>> (I resisted the temptation).

[well so far anyway...]

Cheers
Bruce

Robert Iliuta

  • Sr. Member
  • ****
  • Posts: 472
    • View Profile
    • Email
Re: Row ID's
« Reply #3 on: May 10, 2011, 11:50:55 PM »
Hallo Bruce,

This works great for links. Thank you.

How do I get the Row ID from a browse? when a row is selected how can I decrypted?

Thank you,
Robert

Bruce

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 11250
    • View Profile
Re: Row ID's
« Reply #4 on: May 11, 2011, 05:40:06 AM »
Hi Robert,
 
ahh - this is the cunning bit.
Incoming _bidv_ parameters are automatically decoded for you, and the real value is placed in the Value queue (as if it was passed as a parameter).
So you can use p_web.GetValue('cus:id') just like before. That "just works".
 
cheers
Bruce

koen

  • Newbie
  • *
  • Posts: 11
    • View Profile
    • Email
Re: Row ID's
« Reply #5 on: May 23, 2011, 02:30:39 PM »
Hi Bruce,

Hashing the ID is a very very nice and elegant improvement.
As to: p_web.AddBrowseValue('browsecustomers','customers',cus:idkey,2), you mentioned that the procedure name ('browsecustomers') is an internal optimizer. What do you mean by that? From your example I understand that the procedure name is the name of the calling procedure. But I tried example 24 with the procedure name left blanc and that also works. I did notice that the hash differs between the two, but does leaving it empty will potentially impact the performance?

Cheers,

Koen


Bruce

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 11250
    • View Profile
Re: Row ID's
« Reply #6 on: May 23, 2011, 08:48:13 PM »
Hi Rene,

First, let me clarify. Although I called it a hash, a random index is probably a better description. It doesn't use a hashing algorithm, but rather a completely random value (by design). So yes, each time you call it you'll get a different I'd, unless that browse value is already in the queue, in which case you get the current value.

If you are browsing through a large file, the queue would grow, so a mechanism for deleting some values from the queue is required. The procedure name is used for this. There is a method called at the start of the browse procedure which clears the queue each time a new page is generated. This keeps the queue size under control.

Thus using the procedure name is a good idea, although not essential. In my example I showed adding the call to a menu - and then a build or so later I added the Clear method call to the menu template code.

So in short, the procedure name is there for administrative reasons - it's good to use it, but not critical if you don't.

Cheers
Bruce