0

I have a post model in my rails project,

First, I think that exposing the real db id is NOT good idea, am I right?

If I'm right, I hope to generate a unique progressively increasing number, use it in url, just link below line:

https://hostname.com/posts/unique_number

I searched in SO, from below link, SecureRandom can generate random token, but it can't generate progressively increasing number.

Best way to create unique token in Rails?

So, how to generate progressively increasing number before creating new record.

Thanks a lot.

Community
  • 1
  • 1
CloudBSD
  • 106
  • 1
  • 15
  • 3
    If you want a progressively increasing number, why not use ID itself? – Santhosh May 02 '14 at 15:02
  • 2
    The purpose of not using incremental ids in url is to prevent people from guessing valid id (like, trying their id - 1). If you implement an other id-like field (using deductible increments), you just duplicate that problem. – kik May 02 '14 at 15:06
  • Olivier EI Mekki is right, the purpose is to prevent people knowing the real db id. I personal thing incremental ids is better. I guess SO questions use incremental number. – CloudBSD May 02 '14 at 15:14
  • When you create a new table, the db id and your increasing number will be equals. Even using a salt: id + salt = increasing number. It's pointless. – dx7 May 02 '14 at 15:23
  • 4
    It's a partially misunderstood OWASP principle. It is ok to expose the db ids, if you *also* check authorisation to view the specific record. It is only a problem to expose predictable ids if the record needs to be kept secure and if you do not or cannot check that authorisation. – Neil Slater May 02 '14 at 15:37
  • @NeilSlater: there are very specific problems where you don't want either authentication nor predictable ids, though, like private gists in gist.github.com (should be easily shared, should not be found without being given the link) – kik May 02 '14 at 15:39
  • @Oliver El Mekki: Yes, but you don't use a sequence there, you would use a uuid. OP seems to want a convenient sequence. – Neil Slater May 02 '14 at 15:51

4 Answers4

3

Depending on the frequency of posts created, you can use Time from epoch for a (almost) unique number which is progressively increasing, either in seconds:

Time.now.to_i

or in milliseconds:

(Time.now.to_f * 1000).to_i

Caveats:

  1. if you have a lot of traffic there is a chance that IDs will be duplicated - if unique is checked on insert, you can recreate the id on uniqueness failure.
  2. in a multi-machine topology, your machines might not be synchronized to the same time, which might create a situation where IDs will be created which are smaller than existing ones.
Uri Agassi
  • 36,848
  • 14
  • 76
  • 93
  • thanks a lot, the unique is necessary. now, only one server with lots of traffic. Do you thing cache a global variable in memory is a good idea, and update it after creating new record. – CloudBSD May 02 '14 at 15:46
  • @CloudBSD - how are you planning on synchronizing it? – Uri Agassi May 02 '14 at 15:48
  • 1
    @CloudBSD - I've added provision for uniqueness (recreate id on retry) – Uri Agassi May 02 '14 at 15:54
  • 1
    @CloudBSD - there would definitely be a hit - you should find some kind of optimistic lock to prevent that https://docs.jboss.org/jbossas/docs/Server_Configuration_Guide/4/html/TransactionJTA_Overview-Pessimistic_and_optimistic_locking.html – Uri Agassi May 02 '14 at 15:56
  • I suppose your Post can not be created by anonymous users ? If so, just hash the timestamp + user id, and you will have something without any risk unique. – kik May 02 '14 at 15:59
  • @UriAgassi, yes. you mentioned recreate it in your answer. so I thing unique condition and increasing conditions are both satisfied. – CloudBSD May 02 '14 at 16:03
  • @OlivierElMekki - yes, only registered user can create post. timestamps + user_id is good idea. – CloudBSD May 02 '14 at 16:05
3

You could (if it's satisfactory) create a unique number that's somewhere between 1 and 10_000 after the last number generated.

So that the 'to_param' can work properly, your token should be a String.

Migrate to create the column

add_column :posts, :unique_number, :string

Then modify the model...

class Post < ActiveRecord::Base
  before_create :new_unique_number

  def new_unique_number 
    last_number = Post.order('unique_number DESC').first.unique_number || '1'
    self.unique_number = (last_number.to_i + rand(1, 10_000)).to_s
  end

  #.....
end
SteveTurczyn
  • 36,057
  • 6
  • 41
  • 53
  • thanks a lot. 1. if the site has lots of traffic, ```new_unique_number``` will not unique, am I right?. 2. I need to query db before creating any post, is this a performance issue? – CloudBSD May 02 '14 at 15:25
  • 1
    It most likely will be unique... if you have two records being created and using the same starting "seed" (highest current record value) it is generating a random number between 1 and 10_000 and it's unlikely that those two simultaneous adds would generate the same random number. But your question about performance is a good one... if you're intending to run this on a single machine I'd go with Uri Agassi's solution, and what Olivier El Mekki says makes sense, too. – SteveTurczyn May 02 '14 at 15:34
1

If you use a Post model, I suppose we speak of blog articles or something ?

The question is : do you really need something incremental, or just something unique ?

If what matters is that your id is unique, you can use anything you want as id, not just an integer.

In model

For example, let say your Post model has a validation on title uniqueness. You can decide its title will be used in urls :

class Post
  validates_uniqueness :title

  def to_param
    title
  end
end

The #to_param method allows to decide which attribute will be used for url generation :

post = Post.create( title: 'my_post' )
post_path( post ) # => /posts/my_post

Note that if you want to use a human string like title, you probably will want to sanitize it in a before save callback and set it in an other field, like "url_title" (to avoid spaces and ugly looking characters in urls).

In controller

In controller, you do just as usual, getting the id in params[:id] :

class PostsController < ApplicationController
  before_action :find_post

  def show
  end

  private

  def find_post
    @post = Post.where( title: params[ :id ] ).first or redirect_to '/'
  end
end
kik
  • 7,867
  • 2
  • 31
  • 32
  • thank you. in my project, incrementing is optional, and unique is necessary. I can't use title as the native language is not english, so using title in url is not suitable for the project. – CloudBSD May 02 '14 at 15:37
  • do you thing using real db id in url is a bad design? thanks. – CloudBSD May 02 '14 at 15:39
  • 1
    It's not bad design, actually it's fairly common. There are a few reasons for which you would not want to have id in urls, though : semi private links, marketing reason (don't want client to know there are only 3 clients before them), seo things (having a keyword meaningful url), etc. – kik May 02 '14 at 15:45
1

First, I think that exposing the real db id is NOT good idea, am I right?

It depends.

If your object IDs come from a predictable sequence, that means someone using your application can guess likely valid IDs, and add them to URLs, POST requests etc.

If the objects are accessed read-only and publicly, then there is no problem at all just exposing the database as it was generated.

If you need to secure access, there is more than one way to do so. If you have secure authorisation in your web application, and can tell whether a current user should be able to see an object or not, then you already have sufficient security. Creating unguessable object ids (e.g. uuids) does not really help you. Using some other sequence to "hide" the id does not really add any security and could be a lot of work. Although you could go a step further and generate friendly URLs derived from other content (title), and that would be a similar effort, that is generally not a security concern.

You should only worry about exposing your DB ids directly if that would give a direct link via the API where unauthorised users could guess other IDs and access or change things they were not supposed to. If you cannot restrict access direct via the ID due to other constraints in the system, the answer is not to generate some other direct, predictable index to the same object (which will have the same problems with people being able to guess it), but do one of:

1) provide indirect access - e.g. user can only choose from objects that you have pre-selected for them, and you assign IDs to those choices instead, and only accept those choice_ids via your API, never the database ids of the objects being chosen.

2) generate unguessable IDs, possibly temporary, like SecureRandom.uuid

Neil Slater
  • 26,512
  • 6
  • 76
  • 94