Sunday, October 28, 2007

Advanced Rails : Why you should not use Global Variables ! [Updated 12th Sept 2009]

What are global variables in Ruby ?

A global variable has a name beginning with $. It can be referred to from anywhere in a program. Before initialization, a global variable has the special value nil.

Global variables should be used sparingly. They are dangerous because they can be written to from anywhere. Overuse of globals can make isolating bugs difficult; it also tends to indicate that the design of a program has not been carefully thought out. Whenever you do find it necessary to use a global variable, be sure to give it a descriptive name that is unlikely to be inadvertently used for something else later (calling it something like $foo as above is probably a bad idea).

When a global variable has been rigged to work as a trigger to invoke a procedure whenever changed, we sometimes call it an active variable. For instance, it is useful for keeping a GUI display up to date.

There is a collection of special variables whose names consist of a dollar sign ($) followed by a single character. For example, $$ contains the process id of the ruby interpreter, and is read-only. Here are the major system variables and their meanings (see the ruby reference manual for details):

$! latest error message
$@ location of error
$_ string last read by gets
$. line number last read by interpreter
$& string last matched by regexp
$~ the last regexp match, as an array of subexpressions
$n the nth subexpression in the last match (same as $~[n])
$= case-insensitivity flag
$/ input record separator
$\ output record separator
$0 the name of the ruby script file
$* the command line arguments
$$ interpreter's process ID
$? exit status of last executed child process

In the above, $_ and $~ have local scope. Their names suggest they should be global, but they are much more useful this way, and there are historical reasons for using these names.

Why you should not not use Global Variables

Let us assume that on login (say using Acts As Authenticated), you may be tempted to initialise say $is_admin = current_user.isadmin and then use $is_admin to control access to modules or whatever. This will work very nicely in development mode or even production mode under say Apache 2.x + 1 mongrel process.

However, once you use Apache 2.2x + a pack of mongrel processes, your security measure will fail miserably ! Why ?

Prior to using Apache+a pack of mongrels, I was succesfully using a home-made role-based authentication in conjunction with AAA.

What I did basically was to add roles in users (AAA table) via adding fields (such as is_admin, is_marketing, is_finance etc). The result is that a user can can have one or many roles.
With this concept, I could control access to modules using layouts and within each controller or model I can control access to specific actions.


1. Control access to specific actions via controller.

EXAMPLE in customers_controller.rb
layout 'customers'
active_scaffold :customer do config

if User.current_user.is_admin
config.nested.add_link("Receivables", [:receivables])
end

2. Control access to specific actions via model

has_many :receivables

def authorized_for_update?
current_user.is_finance
end

def authorized_for_destroy?
current_user.is_admin
end


Using 1-3 works perfectly on Apache+1 mongrel on a Windows 2003. But once I activate Apache + 2 or mongrels, the whole security system got thrashed !


Response from Jan (Mongrel FAQ)

Globals are not shared across different instance of mongrel. To have a global appear in all instances you need some form of permanent storage (filesystem or database). If it doesn't need to change over the lifetime of the program you can pass in the global at startup in an environment variable.

You would not want to use a global in this case, since it needs to change after start up and the other mogrels would not be aware of the change. Use permanent storage. File system, database, or perhaps even a cookie

Response from Luis (author of mongrel windows service)

As Jan pointed, global variables aren't shared across processes.
You need rely your authorization schema in something else instead a global variable, since 2 users could hit the same mongrel instance and both get great as admin.
You need to investigate further about authorization schemas and how session is handled (and could be used for your purpose).

First you need to move your session storage to ActiveRecord or cookie based (in case of 1.2.5 or edge). Then I suggest you take a look at the following plugins for Authentication:

RESTful Authentication:
http://railsify.com/plugins/3-restful-authentication

Acts as Authenticated:
http://technoweenie.stikipad.com/plugins/show/Acts+as+Authenticated

Later you will need some role management (to handle is_admin?) Take a look at the suggestion and the "Elsewhere" part from the Acts as Authenticated stikipad page.



My solution was to use the role_requirement plugin by Tim Harper.

This link is also relevant to our discussion here.

3 comments:

Unknown said...

hello, i learn ruby recently... and now i stuck with role_requirement plugin... do you know how can contact the author of this plugin...???

Ram said...

What shall one do if they have to use global variable & apache2 ? Any suggestion to avoid updation by other users?

Admiral Adney said...

i am facing the same issue as qq ruby on rails consultants are requested to solve this issue.

Welcome to Rails.. Rails... Rails !

In 1995, I started the popular Clipper...Clipper... Clipper website (no blogs then) which was very popular and linked by virtually every Clipper-related site. When I switched to Windows via Delphi in 1997, I started the Delphi... Delphi... Delphi site. In June 2007, I discovered Ruby on Rails and no prize for guessing what I am gonna name this blog. which I started on 2nd October 2007.

As at 10th June 2010, we have 13,364 unique visitors from more than 84 countries such as Angola, Andorra, Argentina, Australia, Austria, Algeria,Barbados, Bosnia and Herzogovina, Belgium, Brazil, Bulgaria, Bangladesh, Belarus, Bolivia, Chile, Cambodia, Cape Vede, Canada, China, Colombia, Costa Rica, Croatia, Cyprus, Czech Republic, Denmark, Egypt, Estonia, Finland, France, Guadeloupe, Guatemala, Germany, Greece, Hong Kong, Hungary, India, Indonesia, Ireland, Israel, Italy, Japan, Kenya, Korea, Lithuania, Latvia, Malaysia, Mexico, Macao, Netherlands, Nepal, Norway, New Zealand, Oman, Panama, Peru, Poland, Portugal,Paraguay , Philippines, Romania, Russian Federation, Saudi Arabia, Singapore, Spain, Slovakia, Slovenia, Serbia, South Korea, Slovenia, South Africa, Spain, Switzerland, Sri Lanka, Sweden, Taiwan, Thailand, Turkey, United Arab Emirates, Ukraine, USA, UK, Venezuela, Vietnam

CCH
10th June 2010, 19:42